锁降级:从写锁降到读锁,安全!锁升级:从读锁升到写锁,死锁!
一、什么是锁降级?
锁降级(Downgrade)✅
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock(); // 1. 获取写锁
try {
// 修改数据
data = newData;
lock.readLock().lock(); // 2. 获取读锁(降级开始)
} finally {
lock.writeLock().unlock(); // 3. 释放写锁(降级完成)
}
try {
// 4. 持有读锁,读取数据
return data;
} finally {
lock.readLock().unlock(); // 5. 释放读锁
}
顺序: 写锁 → 读锁 → 释放写锁 = 降级成功
二、为什么不能锁升级?
锁升级(Upgrade)❌会死锁
lock.readLock().lock(); // 1. 获取读锁
try {
// 读取数据
// 尝试升级
lock.writeLock().lock(); // 2. 💣 死锁!
try {
// 永远执行不到
} finally {
lock.writeLock().unlock();
}
} finally {
lock.readLock().unlock();
}
原因:
线程A持有读锁 → 请求写锁 → 等待读锁释放
线程B持有读锁 → 也请求写锁 → 等待读锁释放
所有读锁都在等待写锁 → 死锁!
三、完整的锁降级示例
缓存更新场景
public class CachedData {
private Object data;
private volatile boolean cacheValid;
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData() {
rwl.readLock().lock(); // 获取读锁
if (!cacheValid) {
// 缓存失效,需要更新
rwl.readLock().unlock(); // 释放读锁
rwl.writeLock().lock(); // 获取写锁
try {
// 再次检查(可能有其他线程已更新)
if (!cacheValid) {
data = loadData(); // 加载数据
cacheValid = true;
}
// ✅ 锁降级:获取读锁
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 释放写锁(降级完成)
}
}
try {
return data; // 持有读锁返回数据
} finally {
rwl.readLock().unlock();
}
}
private Object loadData() {
// 从数据库或远程加载
return new Object();
}
}
四、锁降级的好处
不降级版本
public Object getData() {
rwl.writeLock().lock();
try {
if (!cacheValid) {
data = loadData();
cacheValid = true;
}
return data;
} finally {
rwl.writeLock().unlock();
}
}
// ❌ 问题:读也用写锁,性能差
降级版本
// ✅ 写锁 → 读锁
// 好处:数据更新后,其他线程可以并发读取
五、StampedLock的锁转换
支持锁升级(特殊)
StampedLock lock = new StampedLock();
long stamp = lock.readLock(); // 读锁
try {
// 需要写
long ws = lock.tryConvertToWriteLock(stamp); // 尝试升级
if (ws != 0L) {
stamp = ws; // 升级成功
// 修改数据
} else {
// 升级失败,先释放读锁,再获取写锁
lock.unlockRead(stamp);
stamp = lock.writeLock();
// 修改数据
}
} finally {
lock.unlock(stamp);
}
六、对比表
| 特性 | ReentrantReadWriteLock | StampedLock |
|---|---|---|
| 锁降级 | ✅ 支持 | ✅ 支持 |
| 锁升级 | ❌ 死锁 | ⚠️ 尝试转换 |
| 可重入 | ✅ 支持 | ❌ 不支持 |
| 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
七、面试高频问答💯
Q: 为什么支持锁降级但不支持升级?
A:
- 降级:写锁是独占的,降级为读锁不影响其他线程
- 升级:读锁是共享的,升级需要等待其他读锁释放,容易死锁
Q: 锁降级的步骤是什么?
A:
- 持有写锁
- 获取读锁
- 释放写锁
- 持有读锁
Q: 如何实现类似锁升级的效果?
A: 先释放读锁,再获取写锁(但要重新检查条件)。
下一篇→ SynchronousQueue:零容量的神奇队列🎭