reentrantreadwritelock不支持锁升级,持读锁后调用writelock().lock()会因aqs校验直接入队park导致死锁;正确做法是用trylock()避免阻塞,锁降级仅适用于写锁→读锁→释放写锁的单向流程。

ReentrantReadWriteLock 不支持锁升级,这是设计决定,不是 bug。一旦线程持有读锁,再调用 writeLock().lock(),就会进入无限等待状态——这不是卡在读锁,而是卡在写锁获取环节,且永远无法唤醒。
为什么读锁内 lock() 写锁必然死锁
核心原因在于 AQS 的校验逻辑:当线程已持读锁时,writeLock.lock() 会先检查是否有其他写锁;发现没有后,紧接着检查「当前线程是否已持读锁」→ 是 → 直接拒绝,不抢占、不重试,直接入队 park。
- 读锁未释放,写锁又拿不到,AQS 队列中该线程持续 WAITING(线程 dump 显示为
parking) - 其他线程也无法获取写锁(因读锁未释放),读锁也被阻塞(因写锁正在排队,读操作需让位于写锁)
- 整个锁资源被单一线程“半占着”,形成全局阻塞,即死锁表象
典型错误场景:缓存加载中的“读-判断-写”链
很多业务代码模仿 JDK 示例但忽略关键前提,写出如下结构:
- 先
readLock.lock()查缓存 - 发现 miss,不释放读锁,直接
writeLock.lock()想更新 - 结果所有并发线程都在同一行卡住,服务逐渐夯死
这种写法在高并发下极易触发,且无法靠超时或重试缓解——因为 lock() 是无条件阻塞,失败路径根本不存在。
可行替代方案:只依赖 tryLock() 构建安全流程
真正能落地的协调逻辑,必须绕开“持读锁再抢写锁”的路径:
- 优先用
writeLock.tryLock(100, TimeUnit.MILLISECONDS)尝试抢占写锁;成功则双检写入,失败则退避或 fallback 到读操作 - 若必须走读优先路径,只能用
readLock.tryLock()非阻塞获取;失败立即重试或让出 CPU,绝不lock() - 锁降级仅适用于「已稳持写锁 → 获取读锁 → 释放写锁」这一单向流程,且全程不能混入异常分支或异步回调
一个常被误解的关键点
锁降级不是为了“省一次锁开销”,而是保障「写后立刻读」的数据可见性。一旦把它当作通用模式嵌套进 if/else、try/catch 或定时任务里,tryLock() 的失败处理就极易遗漏——此时不如统一用写锁兜底,逻辑更清晰,风险更可控。










