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

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

相关推荐
cike_y4 小时前
Java反序列化漏洞-Shiro721流程分析
java·反序列化·shiro框架
极创信息4 小时前
信创系统认证服务怎么做?从适配到验收全流程指南
java·大数据·运维·tomcat·健康医疗
格鸰爱童话4 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
白宇横流学长4 小时前
停车场管理系统的设计与实现
java
Flittly4 小时前
【SpringAIAlibaba新手村系列】(18)Agent 智能体与今日菜单应用
java·spring boot·agent
木井巳5 小时前
【递归算法】目标和
java·算法·leetcode·决策树·深度优先
亦暖筑序5 小时前
手写 Spring AI Agent:让大模型自主规划任务,ReAct 模式全流程拆解
java·人工智能·spring
敖正炀5 小时前
ReentrantLock 与 synchronized对比
java
XiYang-DING5 小时前
【Java】二叉搜索树(BST)
java·开发语言·python