一文详解Java中的锁降级和锁升级
锁降级(Lock Downgrading)指的是:一个线程在持有写锁的情况下,再获取读锁,然后释放写锁,从而将“写锁”降级为“读锁”的过程。降级后,该线程仍然持有读锁,可以继续读取数据,但其他线程此时可以获取读锁,而写锁依然被排斥。
关键点:必须是“先获取读锁,再释放写锁”
锁降级的正确顺序是:
- 持有写锁
- 获取读锁(此时仍持有写锁)
- 释放写锁
- 继续持有读锁
ReentrantReadWriteLock 支持锁降级,因为写锁是独占的,当前线程持有写锁时再去获取读锁不会被自己阻塞。但反过来,锁升级(持有读锁去获取写锁)是不支持的,会导致死锁,因为读锁是共享的,多个线程可能同时持有读锁,若都试图升级为写锁,会互相等待对方释放读锁。
为什么需要锁降级
典型场景是“写后立即读”且需要保证读到的数据不被其他写线程修改。比如缓存更新:线程修改完数据后,需要基于刚写入的最新值做一些后续读取或计算,如果直接释放写锁再获取读锁,中间可能被其他写线程插入修改,导致读到的不是自己刚写的数据。锁降级保证了从写到读的连续性。
public class CacheService {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void updateAndRead(String key, String value) {
rwLock.writeLock().lock(); // 1. 获取写锁
try {
cache.put(key, value); // 写操作
// 2. 在持有写锁的情况下获取读锁
rwLock.readLock().lock();
} finally {
rwLock.writeLock().unlock(); // 3. 释放写锁,降级完成
}
try {
// 4. 此时仍持有读锁,可安全读取刚写入的数据
String latest = cache.get(key);
System.out.println("降级后读取:" + latest);
} finally {
rwLock.readLock().unlock(); // 5. 释放读锁
}
}
}为什么锁升级的时候会导致死锁呢
锁升级导致死锁的根本原因,在于读锁是共享的,而写锁是独占的。当多个线程同时持有读锁,又都想升级为写锁时,它们会陷入互相等待的僵局。
死锁的形成过程
假设有两个线程 A 和 B,都遵循“先读后升级写”的逻辑:
- A 获取读锁,B 也获取读锁。此时两者共享读锁,互不冲突,都成功拿到。
- A 尝试获取写锁。写锁要求独占,但此时 B 还持有读锁,所以 A 必须等待 B 释放读锁。
- B 也尝试获取写锁。同样,A 还持有读锁,B 必须等待 A 释放读锁。
- 结果:A 等 B 释放读锁,B 等 A 释放读锁,但两者都不会在拿到写锁之前释放自己的读锁——形成循环等待,死锁。
用简单的伪代码表示就是:
// 线程 A 和线程 B 同时执行这段逻辑
rwLock.readLock().lock(); // 第1步:都拿到读锁
try {
// ... 读操作 ...
rwLock.writeLock().lock(); // 第2步:都尝试升级,互相等待对方释放读锁
try {
// ... 写操作 ...
} finally {
rwLock.writeLock().unlock();
}
} finally {
rwLock.readLock().unlock(); // 永远执行不到这里
}为什么获取写锁的时候需要先等别的线程释放读锁。
一、写锁的语义:独占且互斥于所有读
ReentrantReadWriteLock 的规则是:写锁是独占锁,读锁是共享锁,读写互斥、写写互斥、读读共享。这意味着:
- 有读锁存在时,写锁不能获取。
- 有写锁存在时,读锁也不能获取。
这个规则不是随意定的,而是为了保证“写的时候没人读,读的时候没人写”。如果允许写锁在有读锁的情况下获取,那么写线程修改数据的同时,读线程可能正在读取同一份数据,读到的就是半更新状态,违反一致性。
二、为什么“别的线程”的读锁尤其要等
这里的关键是区分当前线程自己持有的读锁和其他线程持有的读锁。
- 别的线程持有读锁:写锁必须等它们全部释放。因为这些读线程正在并发读取数据,写线程一旦介入修改,读线程可能读到新旧混合的数据,产生脏读或不可重复读。写锁作为独占锁,必须确保修改期间没有任何其他线程在读。
- 当前线程自己持有读锁:这就是前面讨论的“锁升级”问题。
ReentrantReadWriteLock同样不允许,因为如果允许自己升级,就无法区分“读锁只被自己持有”(安全)还是“还被别人持有”(危险),而后者正是死锁来源。所以实现上统一处理:只要读锁计数不为 0,获取写锁就失败/阻塞。
到此这篇关于一文详解Java中的锁降级和锁升级的文章就介绍到这了,更多相关Java锁降级和锁升级内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
idea向System.getenv()添加系统环境变量的操作
这篇文章主要介绍了idea向System.getenv()添加系统环境变量的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-06-06
Springboot整合easyexcel实现一个接口任意表的Excel导入导出
本文主要介绍了Springboot整合easyexcel实现一个接口任意表的Excel导入导出,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2025-02-02


最新评论