reentrantreadwritelock 不支持读锁升级为写锁,因设计上禁止同一线程内读写锁交替重入,否则会导致自等待死锁;正确做法是写优先、降级(写→读)、无锁化或分段加锁。

ReentrantReadWriteLock 不支持读锁和写锁的交替重入,即线程在持有读锁的情况下尝试获取写锁,或持有写锁时再获取读锁,会直接阻塞甚至引发死锁风险。这不是锁本身的 bug,而是设计使然——它明确禁止读写锁在同一线程内“升降级”。
为什么读锁后无法升级为写锁
ReentrantReadWriteLock 的设计原则是:读锁可重入(同一线程多次 lock() 读锁),写锁也可重入,但读锁和写锁之间不可相互获取。一旦线程持有了读锁,再调用 writeLock().lock(),该线程就会一直阻塞,因为写锁要求所有读锁释放后才能获得,而当前线程还占着读锁,形成自等待闭环。
- 读锁是共享锁,允许多个线程并发读;写锁是独占锁,必须确保无任何读/写操作
- 若允许读→写升级,会破坏“写锁互斥所有读”的语义,导致数据不一致
- JDK 明确抛出 UnsupportedOperationException(仅限 tryLock 且带超时的场景),但普通 lock() 就是无限期挂起
常见误用模式与排查线索
典型错误发生在缓存读取+按需更新的逻辑中:先用 readLock() 查数据,发现缺失或过期,就试图用 writeLock() 写入——此时线程已卡住。
- 线程堆栈中反复出现 parking to wait for + WriteLock,且持有 ReadLock
- jstack 输出显示该线程 BLOCKED 状态,waiting on condition,锁对象为同一个 ReentrantReadWriteLock$Sync
- 多个线程同时执行类似逻辑,互相阻塞读锁释放,加剧死锁表象(实际是活锁/饥饿)
正确解法:分离读写路径或预判写需求
核心思路是避免“先读后写”的临界区嵌套。要么提前申请写锁(如果写是大概率事件),要么用双重检查+原子引用等替代方案。
- 写优先策略:对可能修改的资源,直接用 writeLock() 进入,查完再决定是否写入;读多写少时开销略大,但逻辑清晰
- 降级写锁(仅限写锁→读锁):writeLock → 操作完成 → unlock 写锁 → 再 lock 读锁,这是安全的;但不能反过来
- 无锁化替代:用 ConcurrentHashMap + computeIfAbsent / CAS 更新,配合 volatile 或 AtomicReference 存储状态,绕过锁竞争
- 分段加锁:将数据划分为多个 segment,每个 segment 独立使用 ReadWriteLock,缩小锁粒度,降低冲突概率
调试与监控建议
生产环境难以复现?可通过日志+线程快照主动暴露问题:
- 在 lock() 前打点日志,记录线程名、锁类型、进入时间;unlock() 后补日志,观察是否匹配
- 用 JMC 或 Arthas watch 命令监控 ReentrantReadWriteLock 的 getReadHoldCount() 和 getWriteHoldCount()
- 设置 JVM 参数 -XX:+UnlockDiagnosticVMOptions -XX:+PrintConcurrentLocks,定期输出锁持有情况
- 单元测试中模拟高并发读写,配合 CountDownLatch 控制时序,强制触发读→写升级路径验证行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











