ReentrantReadWriteLock、ReentrantLock、synchronized 对比

ReentrantReadWriteLock、ReentrantLock、synchronized 全面对比

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


一、核心特性对比

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

二、底层实现与原理

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

三、性能对比与适用场景

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

场景 synchronized ReentrantLock ReentrantReadWriteLock
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 自动优化,不易出错
需要可重入、公平锁、中断、超时、多条件变量 ReentrantLock API 丰富,灵活性高
读操作远多于写操作(如缓存、配置中心) ReentrantReadWriteLock 读并发大幅提升吞吐量
读极多写极少,且不需要可重入、条件等待 StampedLock(非本次对比) 乐观读性能更高

四、代码示例对比

4.1 synchronized 示例

java 复制代码
public class SynchronizedCounter {
    private int count = 0;
    public synchronized void increment() { count++; }
    public synchronized int get() { return count; }
}

4.2 ReentrantLock 示例

java 复制代码
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 示例

java 复制代码
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(); }
    }
}

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

arduino 复制代码
开始
 │
 ├─ 是否需要读锁共享(读多写少)?
 │   ├─ 是 → 是否需要可重入、条件变量?
 │   │       ├─ 是 → 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 替代读写锁:在读极多且不需要重入时,可获得更高性能。

八、总结

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

最终建议

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

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

相关推荐
源码宝1 分钟前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区22 分钟前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
金銀銅鐵30 分钟前
[Java] 如何理解 class 文件中方法的 descriptor?
java·后端
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【63】AI Agent 长期记忆
java·人工智能·spring
憧憬成为java架构高手的小白1 小时前
苍穹外卖--day09
java·spring boot·百度
学代码的真由酱1 小时前
Java多用户一对一网页聊天室-测试报告
java·开发语言·功能测试·测试
Jasonakeke2 小时前
SpringBoot自动配置原理揭秘
java·spring boot·后端
2301_803538952 小时前
Java读取Word图片的两种实用方法
java·开发语言·word
C+-C资深大佬3 小时前
SSM 框架(Spring + SpringMVC + MyBatis)
java·spring·mybatis
帅次3 小时前
Android 17 开发者实战:核心更新与应用场景落地指南
android·java·ios·android studio·iphone·android jetpack·webview