Java读写锁降级

锁降级并不是读锁 / 写锁有 "等级数值",而是指锁的 "独占 / 共享" 状态的转换,且只能从 "更严格的独占状态" 降到 "宽松的共享状态"

一、先搞懂:为什么会有 "锁降级" 这个说法?

读写锁里的 "等级",本质是锁的 "排他性强度"

  • 写锁(排他锁):强度最高 → 独占资源,不允许任何线程(读 / 写)同时访问。
  • 读锁(共享锁):强度更低 → 允许其他读线程共享资源,只排斥写线程。

"锁降级" 就是:持有写锁的线程,在不释放写锁的前提下先获取读锁,再释放写锁,最终只持有读锁。这个过程是 "从高强度的独占锁,降到低强度的共享锁",所以叫 "降级"。

反过来,"锁升级"(读锁→写锁)是不被允许的 ------ 如果一个线程先拿了读锁,再尝试拿写锁,会直接导致死锁(自己等自己释放读锁,其他读线程也在占用,永远等不到)。

二、锁降级的核心目的:保证数据一致性

锁降级不是 "炫技",而是为了在写操作完成后,安全地读取自己刚写入的数据,且期间不让其他写线程插队修改数据。

举个生活例子:

你是仓库管理员(线程),拿着 "仓库钥匙(写锁)" 进去修改库存(写操作)。改完后,你想确认一下改得对不对(读操作),这时候:

  • 如果你先把钥匙还了(释放写锁),再去拿 "查看权限(读锁)",中间可能有其他管理员(其他线程)插队拿钥匙改库存,你看到的就不是自己刚改的数了。
  • 如果你先拿 "查看权限(读锁)",再还钥匙(释放写锁),中间没有空窗期,能确保你看的是自己刚改的数,之后也能和其他员工(读线程)一起查看。
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. 释放读锁 → 所有线程可正常竞争锁
相关推荐
皮皮林5515 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
顺风尿一寸8 小时前
从 Java NIO poll 到 Linux 内核 poll:一次系统调用的完整旅程
java
Flittly8 小时前
【从零手写 ClaudeCode:learn-claude-code 项目实战笔记】(3)TodoWrite (待办写入)
python·agent
程途知微8 小时前
JVM运行时数据区各区域作用与溢出原理
java
华仔啊11 小时前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
千寻girling12 小时前
一份不可多得的 《 Django 》 零基础入门教程
后端·python·面试
xiaoye201813 小时前
Lettuce连接模型、命令执行、Pipeline 浅析
java
databook16 小时前
探索视觉的边界:用 Manim 重现有趣的知觉错觉
python·动效
beata16 小时前
Java基础-18:Java开发中的常用设计模式:深入解析与实战应用
java·后端
Seven9717 小时前
剑指offer-81、⼆叉搜索树的最近公共祖先
java