锁降级并不是读锁 / 写锁有 "等级数值",而是指锁的 "独占 / 共享" 状态的转换,且只能从 "更严格的独占状态" 降到 "宽松的共享状态"。
一、先搞懂:为什么会有 "锁降级" 这个说法?
读写锁里的 "等级",本质是锁的 "排他性强度":
- 写锁(排他锁):强度最高 → 独占资源,不允许任何线程(读 / 写)同时访问。
- 读锁(共享锁):强度更低 → 允许其他读线程共享资源,只排斥写线程。
"锁降级" 就是:持有写锁的线程,在不释放写锁的前提下先获取读锁,再释放写锁,最终只持有读锁。这个过程是 "从高强度的独占锁,降到低强度的共享锁",所以叫 "降级"。
反过来,"锁升级"(读锁→写锁)是不被允许的 ------ 如果一个线程先拿了读锁,再尝试拿写锁,会直接导致死锁(自己等自己释放读锁,其他读线程也在占用,永远等不到)。
二、锁降级的核心目的:保证数据一致性
锁降级不是 "炫技",而是为了在写操作完成后,安全地读取自己刚写入的数据,且期间不让其他写线程插队修改数据。
举个生活例子:
你是仓库管理员(线程),拿着 "仓库钥匙(写锁)" 进去修改库存(写操作)。改完后,你想确认一下改得对不对(读操作),这时候:
- 如果你先把钥匙还了(释放写锁),再去拿 "查看权限(读锁)",中间可能有其他管理员(其他线程)插队拿钥匙改库存,你看到的就不是自己刚改的数了。
- 如果你先拿 "查看权限(读锁)",再还钥匙(释放写锁),中间没有空窗期,能确保你看的是自己刚改的数,之后也能和其他员工(读线程)一起查看。
java
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockDowngradeDemo {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 锁降级的核心方法
public String updateAndRead(String key, String value) {
// 1. 先获取写锁(高强度独占锁),执行写操作
writeLock.lock();
String result = null;
try {
System.out.println(Thread.currentThread().getName() + " 持有写锁,执行写操作");
cache.put(key, value); // 写入数据
// 2. 关键:在释放写锁前,先获取读锁(降级的核心步骤)
// 这一步不会阻塞,因为当前线程已经持有写锁,读写锁允许写线程获取读锁
readLock.lock();
System.out.println(Thread.currentThread().getName() + " 成功获取读锁,准备降级");
} finally {
// 3. 释放写锁,此时线程只持有读锁(完成降级)
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放写锁,完成锁降级");
}
// 4. 持有读锁读取数据,此时其他写线程无法修改,读线程可以共享
try {
System.out.println(Thread.currentThread().getName() + " 持有读锁,读取数据:" + cache.get(key));
result = cache.get(key);
} finally {
// 5. 最后释放读锁
readLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放读锁");
}
return result;
}
public static void main(String[] args) {
LockDowngradeDemo demo = new LockDowngradeDemo();
// 单线程执行锁降级
new Thread(() -> demo.updateAndRead("user", "张三"), "线程A").start();
// 多线程验证:线程A降级后,线程B可以获取读锁,但线程C的写锁会被阻塞
new Thread(() -> {
demo.readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 拿到读锁,读取user:" + demo.cache.get("user"));
} finally {
demo.readLock.unlock();
}
}, "线程B(读)").start();
new Thread(() -> {
demo.writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 拿到写锁,修改user为李四");
demo.cache.put("user", "李四");
} finally {
demo.writeLock.unlock();
}
}, "线程C(写)").start();
}
}
线程A 持有写锁,执行写操作
线程A 成功获取读锁,准备降级
线程A 释放写锁,完成锁降级
线程A 持有读锁,读取数据:张三
线程B(读) 拿到读锁,读取user:张三 // 线程B和A共享读锁,并行执行
线程A 释放读锁
线程C(写) 拿到写锁,修改user为李四 // 只有读锁都释放后,写锁才会执行
text
线程操作流程:
1. 拿写锁 → 独占资源,执行写操作(此时所有读/写线程阻塞)
2. 拿读锁 → 因为当前线程持有写锁,这一步不会阻塞(读写锁的可重入特性)
3. 释放写锁 → 此时线程只持有读锁(完成降级),其他读线程可以加读锁,但写线程仍阻塞
4. 执行读操作 → 安全读取自己刚写的数据
5. 释放读锁 → 所有线程可正常竞争锁