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)

结论:

  • synchronized 和 ReentrantLock 在纯独占场景下性能几乎相同(现代 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 小时前
Spring Boot 链路追踪实战:Micrometer、TraceId 与异步线程上下文传播
java·spring boot·后端
计算机毕设定制辅导-无忧学长1 小时前
《基于Spring Boot的迷游哈游戏综合服务平台的开发》
java·vue.js·spring boot·mysql·迷游哈游戏综合服务平台
非优秀程序员10 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻11 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
文丑颜不良啊13 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang14 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪14 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息15 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼15 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff16 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈