news 2026/1/10 18:19:30

【解析 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/1/9 16:47:10

VideoReTalking技术深度探索:解锁视频配音的无限可能

VideoReTalking技术深度探索&#xff1a;解锁视频配音的无限可能 【免费下载链接】video-retalking [SIGGRAPH Asia 2022] VideoReTalking: Audio-based Lip Synchronization for Talking Head Video Editing In the Wild 项目地址: https://gitcode.com/gh_mirrors/vi/video…

作者头像 李华
网站建设 2026/1/8 15:46:50

测试工程师的沟通与报告技巧

在软件开发生命周期中&#xff0c;测试工程师不仅是质量的守门人&#xff0c;更是信息传递的关键枢纽。高效的沟通能力与精准的报告技巧&#xff0c;直接影响缺陷修复效率、团队信任度及项目决策质量。本文结合行业实践&#xff0c;从沟通原则、报告撰写、工具协同三个维度&…

作者头像 李华
网站建设 2026/1/5 20:16:41

Morisawa BIZ UDGothic 字体完全指南:提升文档易读性的终极选择

Morisawa BIZ UDGothic 字体完全指南&#xff1a;提升文档易读性的终极选择 【免费下载链接】morisawa-biz-ud-gothic 项目地址: https://gitcode.com/gh_mirrors/mo/morisawa-biz-ud-gothic 想要让您的文档和网页设计既专业又易于阅读吗&#xff1f;Morisawa BIZ UDGo…

作者头像 李华
网站建设 2026/1/9 4:00:31

AI代理协作系统部署与监控实战指南

AI代理协作系统部署与监控实战指南 【免费下载链接】crewAI CrewAI 是一个前沿框架&#xff0c;用于协调具有角色扮演能力的自主 AI 代理&#xff0c;通过促进协作智能&#xff0c;使代理能够无缝协作&#xff0c;共同解决复杂任务。 项目地址: https://gitcode.com/GitHub_T…

作者头像 李华