news 2026/4/18 5:46:04

【解析 ReentrantLock】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【解析 ReentrantLock】

目录

  1. 什么是 ReentrantLock?
  2. 为什么需要它?与 synchronized 的核心差异
  3. 如何使用 ReentrantLock?核心 API 与实战
  4. 性能考量:ReentrantLock 总是更快吗?
  5. 最佳实践与常见陷阱
  6. 总结

什么是 ReentrantLock?

ReentrantLock是 Javajava.util.concurrent.locks包下的一个类,它实现了Lock接口。它的全称是“可重入互斥锁”,这个名字包含了两个关键信息:

  1. 互斥锁:和synchronized一样,它在同一时间只允许一个线程持有锁,保证了线程对共享资源访问的原子性。
  2. 可重入:这是它和synchronized共同的一个重要特性。一个已经获取到锁的线程,可以再次进入由该锁保护的任何代码块,而不会自己把自己锁死。
    可重入示例:
publicclassReentrantExample{privatefinalReentrantLocklock=newReentrantLock();publicvoidouterMethod(){lock.lock();try{System.out.println(Thread.currentThread().getName()+": Outer method");innerMethod();// 在锁内调用另一个需要同一把锁的方法}finally{lock.unlock();}}publicvoidinnerMethod(){lock.lock();try{System.out.println(Thread.currentThread().getName()+": Inner method");}finally{lock.unlock();}}}

如果没有“可重入”特性,当outerMethod调用innerMethod时,线程会尝试获取一个它已经持有的锁,从而导致死锁。

为什么需要它?与 synchronized 的核心差异

既然ReentrantLocksynchronized都是可重入的互斥锁,为什么我们还需要ReentrantLock?答案在于它提供了synchronized所不具备的、更强大的功能。

特性synchronizedReentrantLock
锁获取方式隐式获取与释放(JVM 自动管理)手动获取与释放(lock()/unlock()
公平性非公平锁可选择公平或非公平
可中断锁不可中断,等待线程只能一直等可中断lockInterruptibly()
尝试锁不支持支持tryLock()
超时锁不支持支持tryLock(long, TimeUnit)
绑定条件只有一个条件队列(wait()/notify()可绑定多个条件对象Condition

接下来探讨这些核心优势:

1. 公平性
  • 非公平锁(默认):线程获取锁的顺序不一定是它们请求的顺序。JVM 允许“插队”,这可以减少线程上下文切换,提高吞吐量,但可能导致某些线程“饥饿”。
  • 公平锁:严格按照线程请求锁的顺序(FIFO)来分配锁。这保证了每个线程都有机会,避免了饥饿,但性能上会有所损失。
// 创建一个公平锁ReentrantLockfairLock=newReentrantLock(true);
2. 可中断的锁获取

当一个线程等待synchronized锁时,它只能无限期地等下去。而ReentrantLock提供了lockInterruptibly()方法,允许线程在等待锁的过程中响应中断。

try{lock.lockInterruptibly();// 如果线程在等待时被中断,会抛出 InterruptedExceptiontry{// 临界区代码}finally{lock.unlock();}}catch(InterruptedExceptione){// 处理被中断的情况Thread.currentThread().interrupt();// 恢复中断状态}

这在需要优雅取消或超时的任务中非常有用。

3. 尝试锁

tryLock()是一个“非阻塞”的尝试。它会立即返回,告诉你是否成功获取了锁。

if(lock.tryLock()){try{// 成功获取锁,执行临界区代码}finally{lock.unlock();}}else{// 获取锁失败,可以做其他事情,而不是傻等System.out.println("无法获取锁,我先去干点别的...");}

tryLock(long time, TimeUnit unit)更进一步,允许在指定时间内等待,超时后自动返回,这是预防死锁的强大武器。

4. 多条件变量

synchronized只能与一个Object的监视器(wait/notify/notifyAll)配合使用,所有等待的线程都在同一个队列里。ReentrantLock可以通过newCondition()创建多个Condition对象,实现更精细的线程分组和唤醒。
想象一个生产者-消费者场景:

  • Condition notFull:当队列满时,生产者等待此条件。
  • Condition notEmpty:当队列空时,消费者等待此条件。
    这样,你可以精确地唤醒“生产者”或“消费者”,而不是像notifyAll()那样无差别地唤醒所有线程。

如何使用 ReentrantLock?核心 API 与实战

使用ReentrantLock的黄金法则是:finally块中释放锁!这可以确保即使发生异常,锁也总能被释放,避免死锁。

基础用法
classSafeCounter{privateintcount=0;privatefinalReentrantLocklock=newReentrantLock();publicvoidincrement(){lock.lock();try{count++;}finally{lock.unlock();// 必须在 finally 中释放}}publicintgetCount(){lock.lock();try{returncount;}finally{lock.unlock();}}}
使用 Condition 实现生产者-消费者
classBoundedBuffer<T>{privatefinalObject[]items;privateintputIndex,takeIndex,count;privatefinalReentrantLocklock=newReentrantLock();privatefinalConditionnotFull=lock.newCondition();privatefinalConditionnotEmpty=lock.newCondition();publicBoundedBuffer(intcapacity){this.items=newObject[capacity];}publicvoidput(Titem)throwsInterruptedException{lock.lock();try{while(count==items.length){notFull.await();// 队列满,等待 notFull 信号}items[putIndex]=item;if(++putIndex==items.length)putIndex=0;++count;notEmpty.signal();// 唤醒一个等待的消费者}finally{lock.unlock();}}@SuppressWarnings("unchecked")publicTtake()throwsInterruptedException{lock.lock();try{while(count==0){notEmpty.await();// 队列空,等待 notEmpty 信号}Titem=(T)items[takeIndex];items[takeIndex]=null;if(++takeIndex==items.length)takeIndex=0;--count;notFull.signal();// 唤醒一个等待的生产者returnitem;}finally{lock.unlock();}}}

性能考量:ReentrantLock 总是更快吗?

这是一个经典问题。在 JDK 1.5 时代,ReentrantLock的性能确实远优于synchronized。但随着 JVM 对synchronized的持续优化(引入偏向锁、轻量级锁、自旋锁等),在大多数低竞争场景下,两者的性能差距已经非常小,甚至synchronized略有优势,因为它的语法更简单,JVM 可以进行更深层次的优化。
结论:不要为了性能而选择ReentrantLock你应该基于功能需求来选择:

  • 当你需要公平锁、可中断获取、超时获取或多条件变量时,ReentrantLock是不二之选。
  • 对于简单的同步需求,synchronized依然因其简洁性和 JVM 的深度优化而是一个很好的选择。

最佳实践与常见陷阱

  1. 永远在finally块中unlock():这是最重要的规则,没有之一。
  2. 保持锁的粒度尽可能小:只在真正需要保护共享资源的代码块上加锁,尽快释放锁,以提高并发度。
  3. 不要忘记lock()tryLock()成功后,也要记得在finallyunlock()
  4. 避免嵌套锁:和synchronized一样,在持有锁 A 的情况下再去获取锁 B,如果另一个线程持有锁 B 并尝试获取锁 A,就会发生死锁。
  5. lock()unlock()必须成对出现:确保代码逻辑中,每一个lock()都有对应的unlock()

总结

回到我们最初的提问:为什么需要ReentrantLock
它不仅仅是一个锁,更是一个功能丰富的并发控制工具箱。它将锁的控制权从 JVM 交还给了开发者,让我们能够:

  • 选择公平性,在吞吐量和公平性之间做权衡。
  • 响应中断,构建更健壮、可取消的任务。
  • 尝试与超时,有效预防死锁。
  • 精细化唤醒,通过Condition实现复杂的线程协作。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 7:13:47

联想拯救者工具箱:游戏本性能调优的终极指南

联想拯救者工具箱&#xff1a;游戏本性能调优的终极指南 【免费下载链接】LenovoLegionToolkit Lightweight Lenovo Vantage and Hotkeys replacement for Lenovo Legion laptops. 项目地址: https://gitcode.com/gh_mirrors/le/LenovoLegionToolkit 还在为官方软件占用…

作者头像 李华
网站建设 2026/4/18 7:29:04

2025年职业院校技能大赛高职组“区块链技术应用”食品安全溯源智能合约开发与测试参考答案

2025年职业院校技能大赛高职组“区块链技术应用”食品安全溯源&智能合约开发与测试参考答案 文章目录 2025年职业院校技能大赛高职组“区块链技术应用”食品安全溯源&智能合约开发与测试参考答案 竞赛试题: 模块二:智能合约开发与测试(30分) 任务2-1:智能合约设计…

作者头像 李华
网站建设 2026/4/18 3:49:12

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输…

作者头像 李华
网站建设 2026/4/18 7:01:32

AI 狂飙时代:IT 从业者会被“清理门户”,还是“原地飞升”?

&#x1f32a;️ 前言&#xff1a;暴风眼中的焦虑 “我的代码 AI 写得比我快&#xff0c;还比我 Bug 少。” “Devin 都能自己接单了&#xff0c;我还学 Java 干什么&#xff1f;” 2024-2025 年&#xff0c;IT 行业仿佛经历了一场“三体人”的入侵。从 ChatGPT 到 DeepSeek&am…

作者头像 李华
网站建设 2026/4/14 12:13:26

线代第二章矩阵第四课:方阵的幂

方阵的幂是矩阵运算中的重要内容&#xff0c;只有方阵能定义幂运算&#xff0c;其运算规则和性质有别于普通数的幂运算&#xff0c;下面从定义、核心性质、常用计算方法和典型例题这几个核心方面展开讲解&#xff0c;帮你系统掌握该知识点&#xff1a;基本定义只有 n 阶方阵&am…

作者头像 李华
网站建设 2026/4/18 5:32:32

高效窗口管理新选择:跨平台窗口信息获取工具详解

高效窗口管理新选择&#xff1a;跨平台窗口信息获取工具详解 【免费下载链接】active-win Get metadata about the active window (title, id, bounds, owner, etc) 项目地址: https://gitcode.com/gh_mirrors/ac/active-win 您是否曾经遇到过这样的困扰&#xff1a;在多…

作者头像 李华