Skip to content

java常见的锁


概念一:synchronized(内置锁)

一、是什么?
synchronized 是 Java 内置的互斥锁,基于 JVM 的监视器(Monitor)机制实现。可修饰方法或代码块,保证同一时刻仅一个线程能访问被保护的资源。

二、解决什么问题
解决多线程环境下的共享资源竞争问题,避免数据不一致和竞态条件(Race Condition)。

三、核心方法

  • 修饰实例方法:锁定当前对象实例
  • 修饰静态方法:锁定当前类的 Class 对象
  • 同步代码块:手动指定锁对象(如 synchronized(obj)

四、应用场景

  1. 简单的线程同步(如单例模式的双重检查锁)
  2. 保护共享变量的原子操作
  3. 不需要复杂锁控制的场景(如计数器累加)

五、Java示例

java
public class Counter {
    private int count = 0;
    
    // 同步方法
    public synchronized void increment() {
        count++;
    }
    
    // 同步代码块
    public void add() {
        synchronized(this) {
            count += 2;
        }
    }
}

六、重要注意事项

  1. 自动释放锁:代码块结束或方法返回时自动释放
  2. 可重入性:同一线程可重复获取同一把锁
  3. 性能优化:JDK6+ 引入锁升级机制(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)
  4. 无法中断:等待锁的线程不能被中断
  5. 非公平锁:默认不保证等待线程的获取顺序

概念二:ReentrantLock(显式锁)

一、是什么?
java.util.concurrent.locks.ReentrantLock 是 JDK 提供的显式锁,实现了 Lock 接口,提供比 synchronized 更灵活的锁控制。

二、解决什么问题

  1. synchronized 的局限性:无法中断等待、无法超时获取、不支持公平锁
  2. 需要更细粒度的锁控制(如条件变量)

三、核心方法

  • lock():获取锁(阻塞)
  • unlock():释放锁(需在 finally 中调用)
  • tryLock():尝试非阻塞获取锁
  • lockInterruptibly():可中断获取锁
  • newCondition():创建条件变量

四、应用场景

  1. 需要尝试获取锁或超时控制的场景
  2. 公平锁需求(按等待顺序获取锁)
  3. 复杂同步逻辑(如生产者-消费者模型)
  4. 需要锁统计信息(如获取锁的次数)

五、Java示例

java
import java.util.concurrent.locks.ReentrantLock;

public class SafeCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int value = 0;
    
    public void increment() {
        lock.lock();  // 手动加锁
        try {
            value++;
        } finally {
            lock.unlock();  // 必须手动释放
        }
    }
    
    // 尝试获取锁(超时2秒)
    public boolean tryIncrement() throws InterruptedException {
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            try {
                value++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

六、重要注意事项

  1. 必须显式释放锁:否则会导致死锁
  2. 可重入性:支持同一线程重复加锁
  3. 公平性选择:构造函数可指定公平/非公平模式(默认非公平)
  4. 条件变量:通过 Condition 实现精确的线程唤醒
  5. 锁中断:支持响应线程中断请求

概念三:ReadWriteLock(读写锁)

一、是什么?
java.util.concurrent.locks.ReadWriteLock 是接口,其实现类 ReentrantReadWriteLock 将锁分为:

  • 读锁(共享锁):允许多线程并发读
  • 写锁(排他锁):仅允许单线程写

二、解决什么问题
解决读多写少场景的性能瓶颈:

  • synchronizedReentrantLock 在纯读操作时仍互斥
  • 读写锁允许多线程同时读,提高并发性能

三、核心方法

  • readLock():获取读锁
  • writeLock():获取写锁
  • 支持锁降级(写锁 → 读锁),不支持锁升级

四、应用场景

  1. 缓存系统(如 Redis 的读写分离)
  2. 高频查询低频修改的数据结构(如配置中心)
  3. 需要保证写操作的原子性,同时允许并发读

五、Java示例

java
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
    private Object data;
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    public void updateData(Object newData) {
        rwLock.writeLock().lock();  // 获取写锁
        try {
            data = newData;  // 写操作(独占)
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    public Object readData() {
        rwLock.readLock().lock();  // 获取读锁
        try {
            return data;  // 读操作(共享)
        } finally {
            rwLock.readLock().unlock();
        }
    }
}

六、重要注意事项

  1. 写锁优先级:默认非公平模式下,写锁可插队(避免读锁饥饿)
  2. 锁降级:持有写锁时可获取读锁 → 释放写锁 → 保留读锁(保证数据一致性)
  3. 不支持锁升级:读锁 → 写锁会导致死锁
  4. 重入性:读锁和写锁均可重入

概念四:StampedLock(邮戳锁 - JDK8+)

一、是什么?
java.util.concurrent.locks.StampedLock 是 JDK8 引入的优化读写锁,支持三种模式:

  • 写锁:独占锁
  • 悲观读锁:类似 ReadWriteLock 的读锁
  • 乐观读:无锁读取,通过邮戳(Stamp)验证数据一致性

二、解决什么问题

  1. ReadWriteLock 在读多写少时仍存在性能开销
  2. 提供无锁读机制(乐观读),大幅提升读性能
  3. 解决读写锁的线程饥饿问题

三、核心方法

  • writeLock():获取写锁,返回邮戳
  • readLock():获取悲观读锁,返回邮戳
  • tryOptimisticRead():乐观读(无锁),返回邮戳
  • validate(stamp):验证乐观读期间是否有写操作
  • tryConvertToWriteLock(stamp):尝试升级为写锁

四、应用场景

  1. 极高并发读场景(如金融行情系统)
  2. 读操作远多于写操作(≥ 100:1)
  3. 需要极低延迟的读操作

五、Java示例

java
import java.util.concurrent.locks.StampedLock;

public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();
    
    // 乐观读
    public double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead();  // 无锁读取
        double curX = x, curY = y;
        if (!sl.validate(stamp)) {  // 检查期间是否有写操作
            stamp = sl.readLock();  // 转为悲观读
            try {
                curX = x;
                curY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(curX * curX + curY * curY);
    }
    
    // 写操作
    public void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
}

六、重要注意事项

  1. 不可重入:同一线程重复获取锁会死锁
  2. 无条件变量:不支持 Condition
  3. 乐观读风险:需通过 validate() 严格校验数据一致性
  4. 锁转换:支持悲观读锁 → 写锁升级(需循环尝试)
  5. 适用场景:仅推荐在读操作占绝对优势时使用

概念五:其他锁(简要介绍)

除了上述常见的锁,Java中还有一些其他锁机制,如:

  • Condition:通常与ReentrantLock配合使用,用于实现线程间的条件等待(类似Object.wait()/notify())。
  • LockSupport:提供更底层的线程阻塞和唤醒操作(park/unpark)。
  • Semaphore:信号量,用于控制同时访问特定资源的线程数量。
  • CountDownLatch、CyclicBarrier:用于线程间的同步协作。

锁的核心区别对比

特性synchronizedReentrantLockReadWriteLockStampedLock
锁类型内置锁显式锁读写分离锁优化读写锁
获取方式JVM 自动管理手动 lock/unlock手动获取读/写锁手动管理邮戳
公平锁不支持支持(构造函数)支持(构造函数)不支持
可重入性支持支持支持不支持
条件变量wait/notify支持 Condition支持 Condition不支持
锁中断不支持支持 lockInterruptibly支持仅写锁支持
超时获取不支持支持 tryLock支持 tryLock支持 tryWriteLock
性能(读多写少)极高(乐观读)
JDK 版本所有版本JDK5+JDK5+JDK8+

总结

  1. synchronized:首选简单场景,自动管理锁,性能已优化
  2. ReentrantLock:需高级功能时使用(如公平锁、条件变量)
  3. ReadWriteLock:读多写少场景的标准解决方案
  4. StampedLock:JDK8+ 的极致读性能选择(但需谨慎使用)

锁选择建议

  • 优先用 synchronized(90% 场景够用)
  • 需要灵活控制时选 ReentrantLock
  • 读操作占比 ≥ 80% 时用 ReadWriteLock
  • 极端读密集型(≥ 100:1)且 JDK8+ 时考虑 StampedLock