readwritelock适用于读多写少场景,核心判断是读频远高于写频且读不修改状态;需避免读锁中调写操作、加锁不配对、读锁升级等错误,非公平模式更常用。

ReadWriteLock 是 Java 并发包里专为「读多写少」场景设计的锁协议,它不直接提供锁,而是一套规范——真正干活的是它的实现类,比如 ReentrantReadWriteLock。用对了,读操作能并发执行,吞吐翻倍;用错了,反而比 synchronized 还慢,还容易死锁或数据不一致。
什么时候该用 ReentrantReadWriteLock 而不是 ReentrantLock
核心判断就一条:你的共享数据是否「被读的频率远高于被写的频率」,且读操作本身不修改状态。
- 适合:缓存加载后只读、配置中心快照、只增不删的索引映射表(如
Map<string config></string>) - 不适合:高频更新的计数器、需要读-改-写原子性的场景(如
value++)、写操作占比超过 20% 的业务逻辑 - 特别注意:如果读操作里偷偷调用了可能触发写的方法(比如 getter 里调了
computeIfAbsent),那读锁就形同虚设,必须统一用写锁
readLock() 和 writeLock() 必须配对使用,且不能交叉加锁
这是最常踩的坑:读锁没释放就去抢写锁,或者写锁没释放就试图再拿读锁——都会导致线程永久阻塞。
-
readLock().lock()后必须对应readLock().unlock(),哪怕只是 try-with-resources 也得确认AutoCloseable实现正确(ReentrantReadWriteLock.ReadLock不是自动关闭的) - 写线程可以「降级」:先持
writeLock,再拿readLock,然后释放写锁,保留读锁(用于后续只读校验) - 读线程绝对不能「升级」:持有
readLock时调writeLock().lock()会一直阻塞,JVM 不支持这种升级 - 示例错误写法:
readLock.lock(); try { if (needUpdate()) { writeLock.lock(); // ⚠️ 死锁起点 ... } } finally { readLock.unlock(); }
公平模式 vs 非公平模式:别默认用公平,除非你真在压测中看到写饥饿
ReentrantReadWriteLock 默认是非公平的,这意味着新来的读线程可能插队,把排队等写锁的线程饿很久——但实测中,非公平模式吞吐高 15%~30%,尤其在读压力大的服务里。
- 开启公平只需传
true:new ReentrantReadWriteLock(true) - 公平模式下,写锁永远优先于新读请求;但如果已有大量读锁被持有,写线程仍需等所有读锁释放完才能上
- 真实业务中,95% 场景用非公平就够了;只有监控发现
writeLock等待时间持续 > 50ms,才考虑切公平
为什么 StampedLock 不是 ReadWriteLock 的替代品
很多人听说 StampedLock 性能更好,就直接替换,结果出问题——它不保证锁的可重入性,也不兼容中断,更关键的是:乐观读失败后若没做 validate 检查,会读到脏数据。
-
StampedLock的tryOptimisticRead()返回的是一个 stamp,不是锁,它不阻塞也不注册线程,纯靠事后校验 - 一旦发生写操作,所有未 validate 的乐观读都失效,你得兜底切悲观读,代码复杂度陡增
- 如果你的读操作涉及多个字段、或需要强一致性(比如读取用户余额+冻结金额两个字段),老老实实用
ReentrantReadWriteLock.readLock()更安全











