商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java 中 ReentrantReadWriteLock 锁升级是否被允许及其原因

Java 中 ReentrantReadWriteLock 锁升级是否被允许及其原因

  发布于2026-07-10 阅读(0)

扫一扫,手机访问

ReentrantReadWriteLock 禁止锁升级,这是很多 Java 开发者踩过坑的地方。简单说,如果线程已经持有了读锁,再去调用写锁的 lock() 方法,就会永久阻塞——看起来像死锁,但其实是设计上的有意为之。为什么?根源在 AQS 的那套校验逻辑:它会先检查写锁有没有被其他线程占着,发现没有竞争;然后转身一查,哦,当前线程居然还攥着读锁——直接拒绝,并把线程丢进 AQS 队列里永久 park。于是,读锁没释放,写锁拿不到,其他线程既抢不到写锁(因为读锁还在),连新读请求也被堵住(写锁已经在排队),整个读写锁系统就这么卡死了。

Java 中 ReentrantReadWriteLock 锁升级是否被允许及其原因

这段逻辑放在代码里很容易踩雷。常见的一种错误模式是:先拿读锁查缓存,发现缓存 miss,然后不释放读锁,直接调用写锁 lock()。好几个线程同时跑到这里,全卡住——服务慢慢就夯死了。而且 lock() 是无条件阻塞,没超时也没重试选项,出问题之后只能靠重启或者 dump 现场分析。

为什么锁升级被禁止

核心原因就是那个校验逻辑:AQS 在 writeLock.lock() 内部会检查当前线程是否已持有读锁,一旦发现是真的,立刻中断流程。线程进队列无限 park,自始至终不会醒过来。读锁没释放,写锁排队等读锁,新读请求又被排队写锁挡住——单一线程“半占”锁资源,全局锁状态就炸了。

  • 读锁未释放,写锁又无法获取,线程卡在 WAITING(parking) 状态
  • 其他线程也无法获取写锁(因读锁仍存在),且新读请求也会被阻塞(写锁已在排队)
  • 单一线程“半占”锁资源,导致整个读写锁系统陷入全局阻塞

典型错误代码模式

缓存加载里常见的「读-判断-写」链条最容易触发这事:先 readLock.lock() 查询缓存,发现 miss,不释放读锁,直接动手 writeLock.lock()。多个并发线程同时卡在同一行,服务逐渐僵死。这种写法在高并发下指望超时或重试根本没用,因为 lock() 没有失败路径可言。

唯一安全的替代方式是 tryLock()

真正能落地的协调逻辑必须绕开「持读锁再抢写锁」这个死胡同。推荐的做法是:优先用 writeLock.tryLock(100, TimeUnit.MILLISECONDS) 尝试抢占写锁,成功之后做双检再写,失败则退避或 fallback。如果必须走读优先路径,那就用 readLock.tryLock() 非阻塞获取,失败就立即重试或让出 CPU,千万别碰 lock()。至于锁降级,它的用法仅限于「已稳持写锁 → 获取读锁 → 释放写锁」这一单向流程,中间不能混入异常、异步或定时逻辑。

锁降级不是为了省开销,而是保可见性

锁降级的唯一正当用途是保证「写后立刻读」的数据可见性——在写锁释放前先拿读锁,这样后续读操作才能看到刚写进去的最新值。很多人把它当成通用流程嵌套进 if/else 或 try/catch 里,结果很容易漏掉 tryLock() 的失败处理。一旦失败路径覆盖不全,还不如统一用写锁兜底:逻辑更清晰,风险也更可控。

本文转载于:https://www.php.cn/faq/2799509.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注