一文详解Java中的锁降级和锁升级

 更新时间:2026年11月10日 09:34:07   作者:郭小李  
本文深入解析ReentrantReadWriteLock中的锁降级机制,包括定义、正确顺序、使用场景,以及为什么锁升级会导致死锁,通过实例和伪代码,帮助理解写锁降级为读锁的过程和注意事项,避免并发编程中的常见陷阱,需要的朋友可以参考下

锁降级(Lock Downgrading)指的是:一个线程在持有写锁的情况下,再获取读锁,然后释放写锁,从而将“写锁”降级为“读锁”的过程。降级后,该线程仍然持有读锁,可以继续读取数据,但其他线程此时可以获取读锁,而写锁依然被排斥。

关键点:必须是“先获取读锁,再释放写锁”

锁降级的正确顺序是:

  1. 持有写锁
  2. 获取读锁(此时仍持有写锁)
  3. 释放写锁
  4. 继续持有读锁

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,都遵循“先读后升级写”的逻辑:

  1. A 获取读锁,B 也获取读锁。此时两者共享读锁,互不冲突,都成功拿到。
  2. A 尝试获取写锁。写锁要求独占,但此时 B 还持有读锁,所以 A 必须等待 B 释放读锁。
  3. B 也尝试获取写锁。同样,A 还持有读锁,B 必须等待 A 释放读锁。
  4. 结果: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锁降级和锁升级内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis使用pagehelper插件过程详解

    mybatis使用pagehelper插件过程详解

    这篇文章主要介绍了mybatis使用pagehelper插件过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • java中Timer定时器的使用和启动方式

    java中Timer定时器的使用和启动方式

    这篇文章主要介绍了java中Timer定时器的使用和启动方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Spring MVC 学习 之 - URL参数传递详解

    Spring MVC 学习 之 - URL参数传递详解

    本篇文章主要介绍了SpringMVC-URL参数传递,在学习 Spring Mvc 过程中,有必要来先了解几个关键参数,有兴趣的可以了解一下。
    2017-01-01
  • SpringBoot多模块项目创建和初始化问题

    SpringBoot多模块项目创建和初始化问题

    本文详细介绍了使用SpringBoot框架创建一个包含数据库操作、MyBatis-Plus、Knife4j、HTTP请求响应等的Java Web项目的步骤和配置方法,内容涵盖创建项目结构、配置数据库连接、添加依赖、编写接口、处理异常、序列化和反序列化、配置接口文档等等内容
    2026-05-05
  • Java的编译时错误和运行时错误问题

    Java的编译时错误和运行时错误问题

    这篇文章主要介绍了Java的编译时错误和运行时错误问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • idea向System.getenv()添加系统环境变量的操作

    idea向System.getenv()添加系统环境变量的操作

    这篇文章主要介绍了idea向System.getenv()添加系统环境变量的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java语言实现Blowfish加密算法完整代码分享

    Java语言实现Blowfish加密算法完整代码分享

    这篇文章主要介绍了Java语言实现Blowfish加密算法完整代码分享,简单介绍了blowfish加密算法,具有一定借鉴价值,需要的朋友可以参考下。
    2017-11-11
  • Springboot整合easyexcel实现一个接口任意表的Excel导入导出

    Springboot整合easyexcel实现一个接口任意表的Excel导入导出

    本文主要介绍了Springboot整合easyexcel实现一个接口任意表的Excel导入导出,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • 实例讲解Java HashSet

    实例讲解Java HashSet

    这篇文章主要介绍了Java HashSet的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • 在Java中FreeMarker 模板来定义字符串模板

    在Java中FreeMarker 模板来定义字符串模板

    这篇文章主要介绍了在Java中FreeMarker 模板来定义字符串模板,文章基于Java的相关资料展开详细内容,需要的小伙伴可以参考一下
    2022-04-04

最新评论