news 2026/2/17 4:35:50

揭秘ReentrantLock:Java并发编程中的超级锁利器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
揭秘ReentrantLock:Java并发编程中的超级锁利器

好的,我将为你撰写一篇关于ReentrantLock实现原理的技术文档,按照你要求的三个方面进行分析,并采用诙谐幽默的比喻和实战例子来增强可读性。主要内容如下:

  • ReentrantLock是什么:介绍ReentrantLock的定义和核心特点,使用生活化的比喻解释其基本概念。
  • ReentrantLock的核心特性:通过表格对比和代码示例详细说明可重入性、公平性等核心特性。
  • ReentrantLock的实现原理:深入剖析AQS框架、加锁解锁过程以及公平/非公平锁的实现差异,附带源码分析。

接下来,我将开始撰写文章正文。


揭秘ReentrantLock:Java并发编程中的超级锁利器

1 什么是ReentrantLock?:从厕所排队说起

想象一下这样的场景:一个热门商场的厕所,很多人同时想使用,但每次只能进去一个人。这种"独占"使用厕所的场景,在并发编程中就是典型的互斥访问问题。Java中的ReentrantLock(可重入锁)就是解决这类问题的一种高效工具,它提供了比传统的synchronized关键字更强大、更灵活的锁机制。

官方定义ReentrantLock是Java并发包(java.util.concurrent.locks)中的一种可重入互斥锁,它具有与synchronized相同的并发性和内存语义,但增加了更多高级功能。

简单来说,ReentrantLock就像是一个智能门禁系统,它有三个核心特点:

  • 互斥性:像厕所门锁一样,一次只允许一个线程进入
  • 可重入性:如同你有权限重复进入同一个厕所(虽然比喻有点奇怪),同一线程可以多次获取同一把锁
  • 灵活性:提供公平性选择、可中断的锁获取、超时机制等高级功能

synchronized相比,ReentrantLock就像是功能更全面的"高级门禁系统"。synchronized是Java内置的关键字,使用简单但功能有限;而ReentrantLock是一个完整的类,提供了更多精细控制能力。

基本用法先睹为快:

ReentrantLocklock=newReentrantLock();// 创建非公平锁// ReentrantLock lock = new ReentrantLock(true); // 创建公平锁publicvoidcriticalSection(){lock.lock();// 获取锁try{// 临界区代码 - 你的宝贵资源访问在这里System.out.println("线程"+Thread.currentThread().getName()+"正在操作共享资源");}finally{lock.unlock();// 必须确保释放锁}}

注意:lock.unlock()必须放在finally块中,确保即使发生异常也能释放锁,避免死锁。

理解了ReentrantLock的基本概念后,接下来我们看看它到底有哪些令人惊艳的特性,让它成为并发编程中的"明星组件"。

2 ReentrantLock的核心特性:不只是个锁那么简单

如果把synchronized比作一把普通门锁,那么ReentrantLock就是一把智能指纹锁,它提供了丰富多样的高级功能,满足各种复杂场景的需求。让我们通过一个对比表格直观感受两者的区别:

表:ReentrantLock与synchronized特性对比

特性ReentrantLocksynchronized
实现层面API层面(JUC包)JVM层面(关键字)
锁的获取可尝试、可定时、可中断只能阻塞等待
公平性可选公平锁或非公平锁只有非公平锁
条件队列可绑定多个Condition只有一个等待池
释放保证必须手动在finally中unlock()自动释放,由JVM保证

2.1 可重入性:递归调用的"通行证"

可重入性是ReentrantLock的核心特性之一。想象一下,你进入一个房间后,发现里面还有个内门需要同一把钥匙打开。可重入锁就允许你用同一把钥匙打开内门,而不会被自己挡在门外。

在技术层面,可重入意味着:同一线程可以多次获取同一把锁而不会被阻塞。这对于递归调用或者多个方法需要同一把锁的场景至关重要。

publicclassRecursiveExample{privatefinalReentrantLocklock=newReentrantLock();publicvoidouter(){lock.lock();// 第一次获取锁try{inner();// 调用需要同一把锁的方法System.out.println("外部方法执行,锁重入次数: "+lock.getHoldCount());// 查看重入次数}finally{lock.unlock();}}publicvoidinner(){lock.lock();// 第二次获取同一把锁(重入)try{// 一些操作System.out.println("内部方法执行,当前重入次数: "+lock.getHoldCount());}finally{lock.unlock();}}}

如果没有可重入性,当线程在inner()方法中尝试获取锁时,会因为自己已经持有锁而被阻塞,导致死锁。而ReentrantLock通过内部计数器跟踪重入次数,每次lock()时计数器加1,每次unlock()时计数器减1,直到计数器为0时锁才真正释放。

2.2 公平性与非公平性:排队还是插队?

ReentrantLock提供了公平锁非公平锁两种模式,这体现了它在锁策略上的灵活性。

  • 公平锁new ReentrantLock(true)):像银行排队一样,先来后到,保证等待时间最长的线程优先获取锁
  • 非公平锁new ReentrantLock(false),默认):像高峰期挤地铁,允许插队,新来的线程可能比先等待的线程先拿到锁

性能权衡:公平锁保证了公平性,但性能较低(线程切换频繁);非公平锁虽然不公平,但吞吐量更高。在大多数场景下,非公平锁是更好的选择,因为它能减少线程切换的开销。

2.3 尝试锁与可中断:灵活的资源获取策略

ReentrantLock提供了多种灵活的锁获取方式,避免线程无限期阻塞:

尝试锁tryLock):像等电梯时设定时间限制,如果等太久就走楼梯

publicbooleantryIncrement(longtimeout,TimeUnitunit){try{if(lock.tryLock(timeout,unit)){// 尝试在指定时间内获取锁try{// 在指定时间内成功获取锁,执行操作counter++;returntrue;}finally{lock.unlock();}}else{// 超时未获取锁,执行备用方案System.out.println("获取锁超时,执行备用逻辑");returnfalse;}}catch(InterruptedExceptione){Thread.currentThread().interrupt();returnfalse;}}

可中断锁:在等待锁的过程中可以响应中断请求,像排队时接到重要电话可以暂时离开。

publicvoidinterruptibleLock(){try{lock.lockInterruptibly();// 可中断地获取锁try{// 执行操作while(!Thread.currentThread().isInterrupted()){// 检查中断状态}}finally{lock.unlock();}}catch(InterruptedExceptione){// 处理中断,优雅退出System.out.println("锁获取被中断,优雅退出");Thread.currentThread().interrupt();}}

2.4 条件变量:精细化的线程协调机制

synchronizedwait()/notify()配合使用,但只能有一个等待条件;而ReentrantLock可以创建多个条件变量(Condition),实现更精细的线程协调。

在生产者-消费者模型中,这一特性特别有用:

publicclassBoundedBuffer<T>{privatefinalReentrantLocklock=newReentrantLock();privatefinalConditionnotFull=lock.newCondition();// 非满条件privatefinalConditionnotEmpty=lock.newCondition();// 非空条件privatefinalObject[]items=newObject[100];privateintputptr,takeptr,count;publicvoidput(Tx)throwsInterruptedException{lock.lock();try{while(count==items.length){notFull.await();// 等待"非满"条件}items[putptr]=x;if(++putptr==items.length)putptr=0;++count;notEmpty.signal();// 通知"非空"条件已满足}finally{lock.unlock();}}publicTtake()throwsInterruptedException{lock.lock();try{while(count==0){notEmpty.await();// 等待"非空"条件}Tx=(T)items[takeptr];if(++takeptr==items.length)takeptr=0;--count;notFull.signal();// 通知"非满"条件已满足returnx;}finally{lock.unlock();}}}

通过使用不同的Condition,我们可以精确控制哪些线程被唤醒,避免synchronizednotifyAll()带来的"惊群效应"。

了解了这些强大特性后,你可能好奇:ReentrantLock是如何在底层实现这些功能的呢?接下来我们就深入其核心实现原理。

3 ReentrantLock的实现原理:深入AQS核心

要理解ReentrantLock的工作原理,我们需要先认识它的基石AQS(AbstractQueuedSynchronizer),即抽象队列同步器。AQS是Java并发包的核心框架,ReentrantLock的所有功能都建立在AQS之上。

3.1 AQS:并发框架的核心引擎

AQS可以看作是一个同步状态的管理器,它内部维护了三个关键组件:

  1. state(状态字段):volatile int类型变量,表示锁的状态

    • 对于ReentrantLockstate = 0表示锁未被占用
    • state > 0表示锁被占用,数值表示重入次数
  2. 独占线程:记录当前持有锁的线程

  3. CLH队列:一个虚拟的双向队列,用于管理等待锁的线程

AQS使用了模板方法模式,它定义了获取锁和释放锁的骨架,而具体的获取/释放逻辑则由子类实现。这种设计让AQS成为了一个强大的同步框架。

3.2 加锁过程剖析:以非公平锁为例

当我们调用lock.lock()时,背后发生了什么?让我们以默认的非公平锁为例深入分析:

// NonfairSync的加锁过程finalvoidlock(){if(compareAndSetState(0,1)){// 1. 首先尝试CAS快速获取锁setExclusiveOwnerThread(Thread.currentThread());// 成功:设置当前线程为独占者}else{acquire(1);// 2. 失败:进入AQS获取流程}}// AQS的acquire方法publicfinalvoidacquire(intarg){if(!tryAcquire(arg)&&// 3. 再次尝试获取锁acquireQueued(addWaiter(Node.EXCLUSIVE),arg))// 4. 失败后加入队列并阻塞selfInterrupt();}

这个过程可以类比为医院挂号的场景:

  1. 直接尝试(插队):新来的患者(线程)先不看排队情况,直接问挂号窗口:“现在能挂吗?”(CAS操作)
  2. 快速成功:如果恰好没人挂号(state = 0),直接成功,避免排队开销
  3. 正式排队:如果窗口有人(state ≠ 0),则乖乖去排队(进入CLH队列)
  4. 队列中等待:在队列中耐心等待,轮到自已时再次尝试

非公平锁的tryAcquire实现

// NonfairSync的tryAcquire实现protectedfinalbooleantryAcquire(intacquires){finalThreadcurrent=Thread.currentThread();intc=getState();// 获取当前状态if(c==0){// 情况1:锁未被占用if(compareAndSetState(0,acquires)){// CAS尝试获取setExclusiveOwnerThread(current);returntrue;}}elseif(current==getExclusiveOwnerThread()){// 情况2:重入intnextc=c+acquires;// 增加重入次数if(nextc<0)// 溢出检查thrownewError("Maximum lock count exceeded");setState(nextc);returntrue;}returnfalse;// 获取失败}

3.3 释放锁过程:唤醒后续等待者

释放锁的过程相对简单,主要工作是状态恢复唤醒后继线程

// ReentrantLock的unlock方法publicvoidunlock(){sync.release(1);// 委托给AQS的release方法}// AQS的release方法publicfinalbooleanrelease(intarg){if(tryRelease(arg)){// 尝试释放Nodeh=head;if(h!=null&&h.waitStatus!=0)unparkSuccessor(h);// 唤醒队列中的下一个线程returntrue;}returnfalse;}// Sync的tryRelease实现protectedfinalbooleantryRelease(intreleases){intc=getState()-releases;// 减少重入次数if(Thread.currentThread()!=getExclusiveOwnerThread())thrownewIllegalMonitorStateException();// 只有持有者能释放booleanfree=false;if(c==0){// 完全释放free=true;setExclusiveOwnerThread(null);}setState(c);returnfree;}

释放过程的关键点是:只有当重入次数减到0时,锁才真正释放,此时才会唤醒等待队列中的线程。

3.4 公平锁 vs 非公平锁的实现差异

公平锁与非公平锁的核心区别体现在tryAcquire方法的实现上:

// 公平锁的tryAcquire方法protectedfinalbooleantryAcquire(intacquires){finalThreadcurrent=Thread.currentThread();intc=getState();if(c==0){// 关键区别:多了hasQueuedPredecessors()检查!if(!hasQueuedPredecessors()&&// 检查队列中是否有等待更久的线程compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(current==getExclusiveOwnerThread()){// 重入逻辑与非公平锁相同intnextc=c+acquires;if(nextc<0)thrownewError("Maximum lock count exceeded");setState(nextc);returntrue;}returnfalse;}

hasQueuedPredecessors()方法是公平性的守护者,它检查同步队列中是否有比当前线程等待时间更长的线程。如果有,当前线程就不能"插队",必须乖乖排队。

3.5 正确使用ReentrantLock的注意事项

虽然ReentrantLock功能强大,但使用不当会导致严重问题。以下是几个关键实践要点:

1. lock()必须在try外部调用

// 正确写法publicvoidcalculate(){lock.lock();// lock()在try外面try{// 临界区代码intresult=100/0;// 可能抛出异常}finally{lock.unlock();}}// 错误写法(可能导致异常信息被覆盖)publicvoidcalculate(){try{lock.lock();// 错误:lock()在try内部intresult=100/0;}finally{lock.unlock();}}

2. 必须使用try-finally确保锁释放

publicvoidriskyMethod(){lock.lock();try{// 可能抛出异常的代码dangerousOperation();}finally{lock.unlock();// 保证无论发生什么,锁都会被释放}}

3. 避免在lock()和try之间插入代码

publicvoidproblematicMethod(){lock.lock();intnum=1/0;// 危险:在加锁后、try之前可能抛出异常!try{// 临界区代码}finally{lock.unlock();}}

遵循这些最佳实践,可以避免常见的陷阱,确保ReentrantLock的正确使用。

4 实战应用与总结

4.1 实战场景举例

场景1:高性能计数器

publicclassHighPerformanceCounter{privatefinalReentrantLocklock=newReentrantLock();privateintcount=0;publicvoidincrement(){lock.lock();try{count++;}finally{lock.unlock();}}// 使用tryLock实现非阻塞版本publicbooleantryIncrement(){if(lock.tryLock()){try{count++;returntrue;}finally{lock.unlock();}}returnfalse;}}

场景2:简单的阻塞队列

publicclassSimpleBlockingQueue<T>{privatefinalQueue<T>queue=newLinkedList<>();privatefinalReentrantLocklock=newReentrantLock();privatefinalConditionnotEmpty=lock.newCondition();privatefinalConditionnotFull=lock.newCondition();privatefinalintcapacity;publicvoidput(Titem)throwsInterruptedException{lock.lock();try{while(queue.size()==capacity){notFull.await();// 等待"非满"条件}queue.offer(item);notEmpty.signal();// 通知"非空"条件}finally{lock.unlock();}}publicTtake()throwsInterruptedException{lock.lock();try{while(queue.isEmpty()){notEmpty.await();// 等待"非空"条件}Titem=queue.poll();notFull.signal();// 通知"非满"条件returnitem;}finally{lock.unlock();}}}

4.2 总结与选型建议

ReentrantLock是Java并发编程中的重要工具,它基于AQS实现了高效、可重入的锁机制。通过分析源码,我们了解了:

  • 全局结构:Sync、NonfairSync和FairSync的分工协作
  • 核心逻辑:state管理锁状态,CAS确保原子性
  • 生命周期:初次上锁依赖CAS,重入时更新state,释放时递减state
  • 公平性:非公平锁高吞吐,公平锁防饥饿

选型建议

  • 首选synchronized:简单场景,不需要ReentrantLock的高级功能时
  • 需要高级功能时选择ReentrantLock:可中断、超时、公平锁、多个条件变量等复杂场景
  • 谨慎使用公平锁:公平锁有性能开销,除非必要(如防止饥饿),否则使用非公平锁
  • 确保正确释放unlock()必须放在finally块中,避免死锁

ReentrantLock提供了比synchronized更精细的锁控制,是处理复杂并发场景的利器。通过深入理解其实现原理,我们能够更好地利用这一强大工具,编写出高效、可靠的并发程序。

参考资料

  1. https://juejin.cn/post/7499317287724597299
  2. https://blog.csdn.net/weixin_45149504/article/details/152175150
  3. https://blog.csdn.net/majianxin1/article/details/102603380
  4. https://blog.csdn.net/weixin_39996605/article/details/148588071
  5. https://blog.csdn.net/2401_87398486/article/details/151581727
  6. https://cloud.tencent.com/developer/article/2298552
  7. https://blog.csdn.net/feiying101/article/details/138394427
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/10 8:02:36

Java面试Redis核心知识点整理!

大家都知道Redis的业务范围是非常广的&#xff0c;但是对于刚入行的小伙伴来说可能也就知道个缓存跟分布式锁。因为Redis的很多功能在一些小企业里&#xff0c;根本是用不到的&#xff0c;得等到并发量到了一定的程度&#xff0c;系统扛不住了&#xff0c;才会用到Redis那些高级…

作者头像 李华
网站建设 2026/2/15 17:09:21

9、数据足迹缩减:存储容量优化策略

数据足迹缩减:存储容量优化策略 1. 数据足迹缩减技术概述 在当今数字化时代,网络速度加快、存储容量增大,但在特定时间和预算内,需要移动、处理和存储的信息也更多。数据足迹缩减(DFR)技术应运而生,其重要性在于提高资源利用效率,满足服务需求。常见的DFR技术和方法包…

作者头像 李华
网站建设 2026/2/16 13:21:26

17、IT 领域的技术解析与服务洞察

IT 领域的技术解析与服务洞察 在当今数字化时代,数据的生成、处理和存储量达到了前所未有的高度。即使在经济危机期间,对数据处理、移动和存储的需求也在持续增长。因此,IT 组织需要在不增加成本和不影响服务交付的前提下,利用现有资源支持业务增长并提供新服务。以下将为…

作者头像 李华
网站建设 2026/2/14 20:56:26

卡顿监测原理

卡顿监测的核心是检测主线程是否被长时间阻塞&#xff0c;导致无法及时更新 UI。卡顿的本质帧率与刷新率iOS 屏幕刷新率&#xff1a;60Hz&#xff08;ProMotion 120Hz&#xff09;每帧理论时间&#xff1a;16.67ms&#xff08;60Hz&#xff09;或 8.33ms&#xff08;120Hz&…

作者头像 李华
网站建设 2026/2/15 6:52:59

[创业之路-733]:CTO - 技术视野、商业理解力、领导力、团队间协作与沟通、团队管理:“技术的战略家 + 商业的合伙人 + 团队的教练”

作为首席技术官&#xff08;CTO&#xff09;&#xff0c;其角色远不止技术专家&#xff0c;更是连接技术、商业与组织战略的关键枢纽。以下是针对CTO核心能力的详细解析&#xff1a;技术视野&#xff08;Technical Vision&#xff09;前瞻性判断&#xff1a;能够洞察行业技术趋…

作者头像 李华
网站建设 2026/2/8 3:52:58

手把手教你用大模型构建知识图谱:从零开始到实际应用的完整指南,小白也能秒变AI大神!

摘 要 针对档案编研成果开发利用不足的问题&#xff0c;提出融合领域本体与大模型的知识图谱构建框架&#xff0c;包含数据层、概念层、抽取层和图谱层&#xff0c;并以电力公司志为案例开展实证。通过文献调研与数据处理构建领域本体&#xff0c;选定大模型并设计提示词&#…

作者头像 李华