ReentrantReadWriteLock、ReentrantLock、synchronized 对比

22 阅读5分钟

ReentrantReadWriteLock、ReentrantLock、synchronized 全面对比

Java 中实现线程同步的主要方式有三种:synchronized(内置锁)、ReentrantLock(可重入独占锁)、ReentrantReadWriteLock(读写锁)。本文从特性、性能、底层实现、适用场景等维度进行深入对比,帮助开发者在不同场景下做出正确的选型。


一、核心特性对比

特性synchronizedReentrantLockReentrantReadWriteLock
锁类型独占锁(排他锁)独占锁读写锁(读共享、写独占)
可重入性✅ 支持✅ 支持✅ 读锁、写锁均支持可重入
公平性❌ 非公平(无法改变)✅ 可选公平/非公平(默认非公平)✅ 可选公平/非公平(默认非公平)
锁降级❌ 不支持❌ 不支持✅ 支持(写锁→读锁)
锁升级❌ 不支持❌ 不支持❌ 不支持(读→写会死锁)
中断响应❌ 不可中断(抛出 InterruptedException 需 wait/join/sleep)lockInterruptibly()✅ 读锁/写锁均支持 lockInterruptibly()
超时获取❌ 不支持tryLock(long, TimeUnit)✅ 读锁/写锁均支持 tryLock(long, TimeUnit)
尝试获取锁❌ 不支持tryLock()✅ 读锁/写锁均支持 tryLock()
条件变量wait()/notify()/notifyAll()Condition(多个)✅ 写锁支持 Condition;读锁不支持
锁信息查询❌ 无 APIgetHoldCount()isHeldByCurrentThread()✅ 提供读锁/写锁的持有计数、等待线程数等
释放方式自动(退出同步块)手动 unlock()(必须在 finally 中)手动 unlock()(必须在 finally 中)
性能(读多写少)低(串行)低(串行)高(读并发)
性能(写多)中(甚至略低于独占锁)

二、底层实现与原理

底层机制原理简述
synchronizedJVM 内置监视器锁(Monitor),通过对象头中的 Mark Word 实现依赖 JVM 的偏向锁、轻量级锁、重量级锁升级机制,基于 monitorenter / monitorexit 字节码指令
ReentrantLockAQS(AbstractQueuedSynchronizer)基于 AQS 的独占模式,通过 CAS 修改 state(0/1),失败则进入 CLH 等待队列,支持公平/非公平
ReentrantReadWriteLockAQS + state 位分割高 16 位存储读锁总重入次数,低 16 位存储写锁重入次数;读锁使用 AQS 共享模式,写锁使用独占模式;读锁重入通过 ThreadLocal 记录

三、性能对比与适用场景

3.1 吞吐量对比(8 核 CPU,读耗时 ≈ 写耗时)

场景synchronizedReentrantLockReentrantReadWriteLock
100% 读低(约 12 ops/us)低(约 12 ops/us)高(约 90 ops/us)
90% 读 + 10% 写低(约 11 ops/us)低(约 11 ops/us)中高(约 42 ops/us)
50% 读 + 50% 写低(约 10 ops/us)低(约 10 ops/us)中(约 15 ops/us)
10% 读 + 90% 写低(约 9 ops/us)低(约 9 ops/us)低(约 9 ops/us)

结论

  • synchronizedReentrantLock 在纯独占场景下性能几乎相同(现代 JVM 对 synchronized 做了大量优化)。
  • ReentrantReadWriteLock 在读多写少时优势巨大,写多时无优势甚至稍差(CAS 开销)。

3.2 适用场景建议

场景推荐锁原因
简单的同步块,代码量少,无需高级功能synchronized简洁,JVM 自动优化,不易出错
需要可重入、公平锁、中断、超时、多条件变量ReentrantLockAPI 丰富,灵活性高
读操作远多于写操作(如缓存、配置中心)ReentrantReadWriteLock读并发大幅提升吞吐量
读极多写极少,且不需要可重入、条件等待StampedLock(非本次对比)乐观读性能更高

四、代码示例对比

4.1 synchronized 示例

public class SynchronizedCounter {
    private int count = 0;
    public synchronized void increment() { count++; }
    public synchronized int get() { return count; }
}

4.2 ReentrantLock 示例

public class ReentrantLockCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
    public void increment() {
        lock.lock();
        try { count++; }
        finally { lock.unlock(); }
    }
    public int get() {
        lock.lock();
        try { return count; }
        finally { lock.unlock(); }
    }
}

4.3 ReentrantReadWriteLock 示例

public class ReadWriteLockCache {
    private final Map<String, Object> cache = new HashMap<>();
    private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    
    public Object get(String key) {
        rw.readLock().lock();
        try { return cache.get(key); }
        finally { rw.readLock().unlock(); }
    }
    
    public void put(String key, Object value) {
        rw.writeLock().lock();
        try { cache.put(key, value); }
        finally { rw.writeLock().unlock(); }
    }
}

五、何时选择哪一种锁?—— 决策树

开始
 │
 ├─ 是否需要读锁共享(读多写少)?
 │   ├─ 是 → 是否需要可重入、条件变量?
 │   │       ├─ 是 → ReentrantReadWriteLock
 │   │       └─ 否 → StampedLock(性能更高)
 │   └─ 否 → 进入独占锁选择
 │
 ├─ 独占锁场景:
 │   ├─ 是否需要高级功能(公平、中断、超时、多条件)?
 │   │   ├─ 是 → ReentrantLock
 │   │   └─ 否 → synchronized(简单可靠)
 │
 └─ 特殊考量:
     ├─ 锁降级需求 → 必须用 ReentrantReadWriteLock
     ├─ 锁升级需求 → 无原生支持,需设计规避
     └─ 性能敏感且读多写少 → 优先 ReentrantReadWriteLock 或 StampedLock

六、注意事项与陷阱

注意事项
synchronized- 不可中断,等待锁时无法响应中断
- 无法设置超时
- 只有一个条件队列(wait/notify 粒度粗)
- 锁信息不可见
ReentrantLock- 必须手动释放锁,务必放在 finally
- 公平锁会降低吞吐量
- 锁重入次数无上限(受 int 最大值限制,但通常不会溢出)
ReentrantReadWriteLock- 禁止锁升级(读→写死锁)
- 降级后必须释放读锁
- 读锁持有时间不宜过长(阻塞写锁)
- 写锁支持 Condition,读锁不支持
- 重入次数上限 65535

七、性能调优建议

  1. 优先使用 synchronized:除非需要高级功能,否则 synchronized 足以应对大多数场景,且代码更简洁。
  2. 使用 ReentrantLock 时考虑非公平:除非严格公平要求,否则非公平模式吞吐量更高。
  3. 读写锁读临界区尽量小:避免长时间持有读锁导致写线程饥饿。
  4. 考虑锁粒度分解:将一个大锁拆分为多个独立锁(如分段锁),减少竞争。
  5. 使用 StampedLock 替代读写锁:在读极多且不需要重入时,可获得更高性能。

八、总结

特性维度synchronizedReentrantLockReentrantReadWriteLock
易用性⭐⭐⭐⭐⭐(自动释放)⭐⭐⭐(手动释放易错)⭐⭐⭐(手动释放,读写分离需理解)
灵活性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
读并发能力⭐⭐⭐⭐⭐
写并发能力⭐⭐⭐⭐⭐⭐⭐⭐⭐
风险中(unlock 遗漏)高(锁升级、降级释放遗漏)

最终建议

  • 简单同步 → synchronized
  • 需要高级功能(公平、中断、超时、多条件) → ReentrantLock
  • 读多写少且需要读并发 → ReentrantReadWriteLock
  • 读极多且无需重入 → StampedLock

根据具体业务场景的读写比例、功能需求和性能目标,选择合适的锁工具是并发编程的关键。