reentrantreadwritelock实现读写分离需明确三点:读锁可重入、写锁独占、读写互斥;写锁阻塞所有新读写锁,读锁间不互斥但被写锁阻塞;写锁可降级为读锁,读锁不可升级为写锁。

Java 里用 ReentrantReadWriteLock 实现读写分离,不是“加个锁就完事”,关键在读锁可重入、写锁独占、读写互斥这三点上——没搞清这点,很容易写出线程安全但性能极差的代码。
读锁和写锁到底谁阻塞谁?
很多人以为“读锁之间不互斥,写锁之间也不互斥”,其实错在后半句:ReentrantReadWriteLock 的写锁是完全独占的,同一时刻最多一个线程持有写锁,且写锁会阻塞所有新读锁和新写锁的获取;而读锁之间不阻塞,但一旦有线程正在持写锁,任何读锁申请都会被挂起。
- 写锁 → 阻塞所有其他写锁 + 所有新读锁
- 读锁 → 不阻塞其他读锁,但会被当前写锁阻塞
- 写锁可以降级为读锁(调用
writeLock().unlock()后再readLock().lock()),但读锁不能升级为写锁(会死锁)
为什么 lock() / unlock() 必须成对出现在 try-finally 中?
ReentrantReadWriteLock 不像 synchronized 那样自动释放,一旦抛异常没走到 unlock(),锁就永远卡住。尤其写锁,一个线程忘解锁,整个写操作就彻底阻塞。
// 正确:必须用 try-finally 保证释放
ReadLock rLock = rwLock.readLock();
rLock.lock();
try {
// 读操作
} finally {
rLock.unlock(); // 这行绝不能少
}
注意:ReentrantReadWriteLock 不支持中断式读锁(lockInterruptibly() 对读锁有效,但 JDK 8+ 默认非公平模式下可能仍会饥饿),如果业务对响应性要求高,得权衡是否启用公平策略(new ReentrantReadWriteLock(true))。
立即学习“Java免费学习笔记(深入)”;
什么时候该用它?别在错误场景硬套
它只适合“读多写少 + 读操作耗时明显”的场景。比如缓存加载、配置中心本地副本更新;但如果写操作频繁(如每秒上百次修改),或者读操作本身极快(如只读几个字段),用它反而增加锁开销,不如直接 synchronized 或 StampedLock。
- ✅ 推荐:静态配置缓存、只读报表数据集、本地元数据快照
- ❌ 慎用:高频增删改的计数器、实时日志聚合、对象内部字段频繁互斥更新
- ⚠️ 注意:锁粒度要匹配业务——别把整个
Map用一把读写锁保护,考虑分段或ConcurrentHashMap
常见误用:锁对象复用错、条件变量混用、忽略可重入陷阱
最典型的是把 ReentrantReadWriteLock 声明为局部变量,或者每次 new 一个新实例——锁对象不同,根本起不到同步作用;还有人拿 Condition 和读锁/写锁混搭,但 readLock().newCondition() 是非法的,只有写锁能创建 Condition。
另外,“可重入”是指同一线程可重复获取同一类型锁(比如两次 writeLock().lock()),但读锁和写锁之间不可重入:已持读锁时再申请写锁,必然阻塞,哪怕同一线程。
真正难调的,往往是多个读写锁嵌套 + 条件等待 + 异常分支遗漏 unlock 的组合,这种地方建议加单元测试跑并发压力,光看代码很难覆盖所有路径。










