readwritelock写饥饿本质是非公平策略下读线程插队导致写线程长期等待,解决关键在于控制锁获取顺序:启用公平模式(new reentrantreadwritelock(true))、避免读锁重入失配、慎用伪锁降级,并评估是否真需该锁。

Java 中 ReadWriteLock 的写饥饿问题,本质是读多写少场景下,读线程持续抢占导致写线程长期无法获取锁。这不是 bug,而是非公平策略的权衡结果。解决关键在于控制“谁该先拿到锁”,而不是单纯加锁或换锁。
启用公平模式(最直接有效)
默认 ReentrantReadWriteLock 是非公平的,新来的读线程可以插队,把已在队列中等待的写线程晾在一边。启用公平模式后,所有请求严格按 FIFO 排队:
- 构造时传
true:new ReentrantReadWriteLock(true) - 一旦有线程在等待(无论读或写),后续请求必须排队,禁止插队
- 实测吞吐量通常下降 15%~30%,尤其在读操作极轻量、并发极高时
- 上线前务必压测:对比公平/非公平在真实流量下的平均写延迟和 P99 写耗时
避免锁重入失配引发的隐性阻塞
写锁获取失败,有时不是因为读线程太多,而是读锁被某个线程反复 lock() 却没配对 unlock():
- 读锁可重入,内部用高 16 位计数;写锁获取前提:读锁计数为 0 且无写锁持有者
- 递归调用、循环读取、异常提前退出等场景容易漏掉
unlock() - 后果是写线程永久卡在
writeLock().lock(),表现为“假饥饿” - 建议用 try-with-resources 封装读锁(需自定义 AutoCloseable 包装类),或严格检查每处
lock()是否有对应finally unlock()
慎用锁降级,改用业务层一致性保障
很多人误以为写完立刻读就是“降级”,但 ReentrantReadWriteLock 不支持真正锁降级:
-
writeLock().unlock()后调readLock().lock(),当前线程和其他线程一样竞争读锁,中间存在数据被其他写线程修改的窗口期 - 强行这么做可能触发
IllegalMonitorStateException或逻辑错误 - 替代方案更可靠:写操作完成后更新一个
volatile long version或AtomicLong timestamp,读操作先读版本再决定是否重试;或改用StampedLock的乐观读 +validate()验证机制
评估是否真需要 ReadWriteLock
不是所有读多写少场景都适合 ReadWriteLock:
- 若写操作极少(如配置加载一次)、读操作极频繁且只读,考虑无锁方案:用
CopyOnWriteArrayList或不可变对象 +final字段 - 若写操作虽少但延迟敏感(如实时指标更新),
StampedLock的写优先策略可能比公平读写锁更合适 - 若读写粒度难以拆分,或临界区本身很短,直接用
synchronized反而更简单、开销更低、无饥饿风险
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











