ReentrantReadWriteLock只允许写锁降级为读锁,禁止读锁升级为写锁以避免死锁;降级需先持写锁、再获读锁、后释写锁,且必须手动按序操作。

Java 中的读写锁(ReentrantReadWriteLock)**不支持写锁降级为读锁以外的“锁升级”**,更准确地说:**它只允许写锁降级为读锁,且必须在持有写锁的前提下进行;但绝对不允许读锁升级为写锁**——这是由设计决定的,目的是避免死锁。
写锁降级是唯一被允许的“降级”,且需严格按顺序操作
写锁降级为读锁,是指线程在已持有写锁的情况下,再获取读锁,然后释放写锁,从而转变为仅持有读锁的状态。这个过程不是自动发生的,需要手动完成,并满足以下条件:
- 必须先成功获取写锁(
writeLock.lock()) - 在写锁未释放前,调用读锁的
lock()获取读锁(此时因当前线程已持写锁,读锁可重入获取) - 随后显式释放写锁(
writeLock.unlock()) - 此时线程仍持有读锁,即完成“降级”
示例代码逻辑:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock writeLock = rwLock.writeLock();
Lock readLock = rwLock.readLock();
writeLock.lock();
try {
// 修改共享数据
data.update();
// 在写锁未释放前,获取读锁(合法,因为写锁可重入读锁)
readLock.lock(); // ✅ 允许
// 立即释放写锁 → 此时线程只持读锁
writeLock.unlock(); // ✅ 必须在 readLock.lock() 之后、readLock.unlock() 之前
// 后续可安全执行只读操作(如返回数据快照)
return data.getSnapshot();
} finally {
// 最后释放读锁
readLock.unlock();
}
为什么不能读锁升级为写锁?
如果允许一个已持有读锁的线程再去尝试获取写锁,会导致死锁风险。例如:
立即学习“Java免费学习笔记(深入)”;
- 线程 A 持有读锁,尝试升级为写锁 → 需等待所有其他读锁释放
- 线程 B 同时持有读锁,也在等待写锁 → 它不会释放读锁,直到拿到写锁
- 二者互相等待,形成死锁
因此,ReentrantReadWriteLock 明确禁止读锁升级。一旦调用 writeLock.lock() 时当前线程只持有读锁,该调用会**阻塞直至所有读锁释放**(包括自己的),这实际上等价于放弃当前读锁并竞争写锁——不是升级,而是“先退再进”。
降级过程中的常见错误
以下做法均不合法或不可靠:
- 先释放写锁,再获取读锁 → 中间存在无锁窗口,数据可能被其他线程修改
- 在未持有写锁时直接调用
readLock.lock()后再试图获取写锁 → 这不是升级,只是普通加锁,且大概率阻塞 - 依赖锁的“自动转换” → Java 锁没有这种机制,一切需手动控制顺序和时机
实际使用建议
写锁降级适合场景:需要先修改数据,再以只读方式对外提供结果(如缓存更新后返回新值),且要求修改与后续读取之间不被其他线程干扰。
- 务必保证
readLock.lock()发生在writeLock.unlock()之前 - 降级后记得在 finally 块中释放读锁,避免锁泄漏
- 若业务逻辑复杂,考虑是否真需降级——有时直接全程持写锁更清晰安全
- 注意:降级不等于“锁优化”,它增加了锁管理复杂度,应仅在确实需要读共享且避免重复加锁时采用
不复杂但容易忽略:降级的关键不在 API 多强大,而在加锁顺序的严格性。只要守住“写→读→写释→读用→读释”这条链,就能安全使用。


















