ReentrantLock 可重入锁lock与unlock方法

本文详细介绍了ReentrantLock中的非公平锁实现机制,从lock、unlock方法入手,分析了AQS(AbstractQueuedSynchronizer)在其中的作用,包括tryAcquire、addWaiter、acquireQueued等关键方法,揭示了非公平锁如何通过CAS操作和节点入队、线程挂起及唤醒等过程来管理锁的获取和释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ReentrantLock 可重入锁 经常会使用到,需要自己使用lock以及unlock方法将代码块同步,并且支持可重入

lock方法

在这里插入图片描述
上面是ReentrantLock中lock方法的实现,就是调用了sync的lock方法。那么sync是个什么东西呢?

在这里插入图片描述
我们可以看到sync是一个抽象类,并且继承了AQS(抽象队列同步器),也可以看出ReentrantLock是通过AQS来实现线程的抢占锁。上面看出sync是一个抽象类,那么就肯定有对应的子类去继承这个抽象类,那么继续往下看看

在这里插入图片描述
查看对应的子类就会发现他有两个子类,从命名上就能看出这两个子类的作用,一个公平锁,一个非公平锁。
公平锁:线程抢占锁需要排队,先等待的先持有锁
非公平锁: 允许后面的线程比之前的线程先持有锁

在这里插入图片描述

对于ReentrantLock 方法而言,默认就是非公平锁的实现,也可以通过boolean类型的参数实现公平锁。
既然默认是非公平锁,那么先看下非公平锁的具体实现
在这里插入图片描述
我们可以看到非公平锁的实现主要有三个方法,下面看看具体的作用
方法1: compareAndSetState 比较并且设则state的指,通过cas操作保证操作的原子性,将state的值从0赋值为1,state是AQS队列的一个属性,就是用来标识锁是否被持有,0表示没有被持有,1表示被持有。
在这里插入图片描述

方法2: 用来记录持有锁的当前线程

到这里我们就可以发现不管队列中有没有线程在等待,只要我存在线程来竞争这把锁,那么我就有可能直接成功,不需要理会队列中正在等待的线程,如果这里抢占锁没有成功,那么将进入第三个方法

方法3:方法3还是AQS中的方法 可以看到方法3里面又分了几个子方法,下面一个一个的分析
在这里插入图片描述
首先是tryAcquire()方法 下图是非公平锁的tryAcquire方法调用的方法

在这里插入图片描述
首先获取了当前线程以及state值,当state值为0时,表示没有线程获取到锁,这时通过CAS操作获取锁,如果获取到了,将当前线程设置为占用锁的线程并返回true,否则返回false。 如果state值不为0时,则校验获取锁的线程是否与当前线程相同,如果不同返回fales,如果相同则返回true。

当tryAcquire返回true时,表示线程获取到锁,方法结束。当tryAcquire返回false时,表示线程没有获取到锁,继续往下走。

接下来是addWaiter()方法
在这里插入图片描述
可以看到先new了一个节点,并且判断了尾节点是否为空,如果尾节点不为空,那么新创建的节点对象的前节点设置为之前的尾节点,并且通过cas操作设置新的尾节点,如果成功则将之前尾节点的后节点设置为当前new的节点。那么新创建的节点就是尾节点,入队成功。
如果失败或者最开始尾节点为空那么都将进入enq()方法,

那么看看这个enq方法
在这里插入图片描述

首先进入该方法就是一个无限循环,
第一步先判断尾节点是否为空,如果为空,则new一个新的节点通过cas设置为头节点,如果成功,则将尾节点也设置为头节点。(设置的头节点是一个哨兵节点,此节点中没有等待的线程)。如果失败那么进入第二次循环,即尾节点非空的情况。
第二步,如果尾节点非空 则先将需入队的节点前节点指向之前的尾节点,并通过CAS操作设置新的尾节点。如果成功,则将之前尾节点的尾指针指向新的尾节点,如果失败,则将进入下一次循环直到成功为止并返回当前尾节点。那么addWaiter方法则到此为止。

那么接下来将是acquire()方法中的acquireQueued()方法,此方法的一个参数为新入队的尾节点,一个为数字1。
在这里插入图片描述
可以看到在设置了两个boolean值之后又进入了一个死循环,先获取当前节点的头节点。
如果头节点等于当前尾节点的前一个节点(即此节点是哨兵节点后一个节点),那么将再次尝试获取一次锁,如果获取成功,那么将设当前节点设置为头节点,并且将当前节点的线程以及前指针都设置为空,并将之前头节点的尾指针置为空(变成一个无任何引用的节点,等待gc回收)并返回false。
如果节点的前指针指向的非头节点,或者再次获取锁失败,则先进入
shouldParkAfterFailedAcquire()方法:
在这里插入图片描述
先获取前节点的waitStatus属性,这个时候没有初始化,为初始值0。
走进else的代码块,将前节点的waitstatus值设置为-1,如果失败再从第一步开始,重新设值,如果成功,则还是进入下一次循环,然后再次进入第一个代码块返回true。

然后就进入parkAndCheckInterrupt()方法,
在这里插入图片描述

可以看到此方法中调用了LockPark的park()方法,未获取到锁的线程就在此处挂起,等待唤醒。

那么我们接下来看一看unlock()方法
在这里插入图片描述
可以看到unlock()方发调用了sync的release方法

在这里插入图片描述
release()也比较简单,先调用了tryRelease()方法。
在这里插入图片描述
先是获取状态值减1,如果当前线程不是获取到锁的线程,那么就是直接抛出异常。如果状态减到0,那么将返回true并将持有锁的线程置为null,否则直接设置状态为减1之后的值,并返回false。(如果不等于0,那么就相当于是获取了多次锁)

那么再回到release方法 返回true之后,将进入代码块。头节点不为空,且之前已经将waitstatus值设置为-1。那么将会调用unparkSuccessor()方法

在这里插入图片描述
将头节点的waitstate又设置为0,并获取头节点的尾指针。这个时候将唤醒头节点下一个节点的线程。然后唤醒的线程又重新执行那个死循环,获取到锁就出来,没有获取到就继续将头节点的waitstatus值设置为-1并挂起线程。

非公平锁和公平锁的差别就是在获取锁的时候不会去校验队列中是否存在排队的线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值