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

这段逻辑放在代码里很容易踩雷。常见的一种错误模式是:先拿读锁查缓存,发现缓存 miss,然后不释放读锁,直接调用写锁 lock()。好几个线程同时跑到这里,全卡住——服务慢慢就夯死了。而且 lock() 是无条件阻塞,没超时也没重试选项,出问题之后只能靠重启或者 dump 现场分析。
核心原因就是那个校验逻辑:AQS 在 writeLock.lock() 内部会检查当前线程是否已持有读锁,一旦发现是真的,立刻中断流程。线程进队列无限 park,自始至终不会醒过来。读锁没释放,写锁排队等读锁,新读请求又被排队写锁挡住——单一线程“半占”锁资源,全局锁状态就炸了。
缓存加载里常见的「读-判断-写」链条最容易触发这事:先 readLock.lock() 查询缓存,发现 miss,不释放读锁,直接动手 writeLock.lock()。多个并发线程同时卡在同一行,服务逐渐僵死。这种写法在高并发下指望超时或重试根本没用,因为 lock() 没有失败路径可言。
真正能落地的协调逻辑必须绕开「持读锁再抢写锁」这个死胡同。推荐的做法是:优先用 writeLock.tryLock(100, TimeUnit.MILLISECONDS) 尝试抢占写锁,成功之后做双检再写,失败则退避或 fallback。如果必须走读优先路径,那就用 readLock.tryLock() 非阻塞获取,失败就立即重试或让出 CPU,千万别碰 lock()。至于锁降级,它的用法仅限于「已稳持写锁 → 获取读锁 → 释放写锁」这一单向流程,中间不能混入异常、异步或定时逻辑。
锁降级的唯一正当用途是保证「写后立刻读」的数据可见性——在写锁释放前先拿读锁,这样后续读操作才能看到刚写进去的最新值。很多人把它当成通用流程嵌套进 if/else 或 try/catch 里,结果很容易漏掉 tryLock() 的失败处理。一旦失败路径覆盖不全,还不如统一用写锁兜底:逻辑更清晰,风险也更可控。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8