AQS - ReentrantLock源码解析

⼀、概述

当我们想要使⽤重⼊锁的时候,使⽤⽅式⼀般是如下3个步骤:
在这里插入图片描述
那么,我们后续就针对这3个步骤对其源码进⾏解析。但是再次之前,我们还是需要再介绍⼀些前提 性的知识点。⾸先就是,关于Sync、FairSync、NonfairSync在哪⾥被使⽤的,如下所示:
在这里插入图片描述
在这里插入图片描述
【解释】
● 通过上图我们可以看到,在ReentrantLock、ReentrantReadWriteLock、Semaphore、 CountDownLatch都可以看到它们被使⽤的痕迹,下⾯部分的源码解析,我们再深⼊的了解它 们。
● 那么关于AQS这个等待的队列,它到底是什么样⼦的呢?我们可以在此先⼤致的有个概念,这也便 于我们后续对AQS概念的理解:
在这里插入图片描述

⼆、创建重⼊锁

● 如上⾯截图中的实例所示,使⽤的是⽆参的构造函数,其源码如下所示:
在这里插入图片描述
【解释】
除了⽆参构造⽅法外,重⼊锁还包含有参的构造函数,如下所示:
【解释】流程图如下所示: NonfairSync的lock⽅法,源码如下所示:
其中,NonfairSync顾名思义是⾮公平锁。所以,当我们使⽤⽆参构造⽅法去创建重⼊锁的时候,底 层使⽤的是⾮公平锁(NonfairSync);
除了⽆参构造⽅法外,重⼊锁还包含有参的构造函数,如下所示:
在这里插入图片描述
当⼊参fair等于false的时候,采⽤的就是⾮公平锁(NonfairSync); 当⼊参fair等于true的时候,采⽤的就是公平锁(FairSync);
在这里插入图片描述

三、加锁源码分析

3.1> 公平锁与⾮公平锁的lock()

NonfairSync的lock⽅法,源码如下所示:
在这里插入图片描述
在这里插入图片描述
FairSync的lock⽅法,源码如下所示:
在这里插入图片描述
⾮公平锁与公平锁的区别就是多了⼀步上来就试图要去抢锁的“野蛮”⾏为,即:不管三七⼆⼗ ⼀,我上来就要去抢,我才不管你前⾯排队多少线程等待着获取锁呢!很像那种去超市结账没有素 质插队的⼈。⽽当抢锁失败了之后,下⾯的流程就跟公平锁⼀样了,都执⾏了acquire(1)去执⾏等 待排队操作。整体流程图如下所示:
在这里插入图片描述

3.2> acquire(1)

既然⽆论是公平锁还是⾮公平锁,他们都有机会调⽤相同的⽅法,即:acquire(1),那么下⾯我们就来着重分析⼀下这个⽅法底层的具体逻辑是什么?⾸先,还是看⼀下该⽅法的源码,如下所示:
在这里插入图片描述
在这里插入图片描述

3.2.1> tryAcquire(arg)

因为⼊参arg=1,所以其实调⽤的是tryAcquire(1),⽬的是进⾏抢锁操作,并返回是否抢锁成功。 针对公平锁还是⾮公平锁,对应的tryAcquire的实现是不同的,如下图所示,所以我们会针对两种 类型的锁进⾏解析。在这里插入图片描述
a> FairSync.tryAcquire(arg)
● 源码和注释如下所示:
在这里插入图片描述
针对公平锁的tryAcquire⽅法,会先通过state的值来判断,如果为0,则表示当前没有线程抢占 锁,如果不为0,则表明锁已经被线程抢占了。
case1> 没⼈抢占锁(state==0),如果不需要排队,那么线程尝试执⾏抢占锁操作,如果抢占 成功,则返回true;如果抢占失败,则返回false。
case2> 有⼈已经抢占了这个锁(state!=0),但是抢占这个锁的线程就是⾃⼰,那么对⾃⼰执 ⾏重⼊加锁操作,返回true;如果不是⾃⼰抢占的锁,那么返回false;
在这里插入图片描述
b> hasQueuedPredecessors()
其中,通过hasQueuedPredecessors()⽅法来判断本线程是否需要排队,如果返回false,则表示 不需要排队;如果返回true,则表示需要排队。
在这里插入图片描述
h != t
【返回false的情况】只有当队列为空(即:没有完成初始化)或是头指针和尾指针都指 向同⼀个元素,表示队列中只有⼀个节点,这时候⾃然⽆需排队,因为队列中的第⼀个节点是 不参与排队的,它持有着同步状态。

(s = h.next) == null● 【返回true的情况】只有⼀个node或者创建了h的后置节点,但是还没有执⾏ h.next=node,如下⾯enq中红框代码并未执⾏的时候
在这里插入图片描述
s.thread != Thread.currentThread()
【返回为false的情况】head的后置节点(即:⻢上就要被激活执⾏的节点)承载的就是本线程。

c> NonfairSync.tryAcquire(arg)
源码和注释如下所示:在这里插入图片描述
在这里插入图片描述
⾮公平锁的nonfairTryAcquire与公平锁的tryAcquire唯⼀区别是,它没有调⽤ hasQueuedPredecessors()⽅法
在这里插入图片描述

3.2.2> addWaiter(Node.EXCLUSIVE)

相关源码和注释如下所示:
在这里插入图片描述
在这里插入图片描述

3.2.3> acquireQueued(…)

在⽅法内,执⾏⽆限for循环。尝试获得代码如下所示:
在这里插入图片描述
在这里插入图片描述
a> shouldParkAfterFailedAcquire(Node pred, Node node)
当获得锁失败之后,判断是否可以执⾏等待操作;具体逻辑是针对pred节点的waitStatus状态来判 断的:
如果是SIGNAL状态,则返回true。
如果是CANCELLED状态,则将pred节点从链表中剔除。
如果是其他状态,则将pred节点的waitStatus状态修改为SIGNAL状态。
源码和注释如下所示:
在这里插入图片描述
在这里插入图片描述
b> parkAndCheckInterrupt()
● 执⾏park操作,并且返回是否被中断的判断结果,源码和注释如下所示:
在这里插入图片描述

四、解锁源码分析

解锁操作源码所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.1> tryRelease

判断是否可以执⾏释放锁的操作,因为c等于getState() - releases,⼀般情况下,线程进⾏第⼀ 次加锁时state等于1,当这个线程执⾏重⼊锁⾏为的时候,那么state会加1;所以,当我们解锁的 时候,也要⼀次次的执⾏减1操作。当c等于0的时候,也就说明所有的锁都已经被解锁了,源码如 下所示:
在这里插入图片描述

4.2> unparkSuccessor(Node node)

该⽅法的⽤途是:如果节点的接班⼈(successor)存在的话,则唤醒它。源码和注释如下所示:
在这里插入图片描述
在这里插入图片描述

五、加锁和解锁的队列演示

5.1> 概述

本节内容,会以AQS在加锁和解锁过程中,对队列节点的操作进⾏演示,在详细介绍演示之前,我 们先来整体看⼀下,如果有线程A、线程B和线程C这三个线程在AQS的调度下,会是什么样⼦的:
在这里插入图片描述
其中,线程A⾸先抢到锁,所以它不需要链表进⾏排队。那么线程B和线程C由于没有抢到锁(由 state是否等于0进⾏判断),那么就需要组成⼀个链表,头节点就是⼀个“空”的节点(即:通过 new Node())创建的。⽽线程B和线程C则会相机组成链表,并调⽤park阻塞操作。等待后续的 unpark解除阻塞。

5.2> 加锁操作队列演示

线程A第⼀个执⾏了lock()⽅法在这里插入图片描述
线程B第⼆个执⾏了lock()⽅法,此时会构建等待队列链表
在这里插入图片描述

线程C执⾏lock()⽅法
在这里插入图片描述

5.3> 解锁操作队列演示

解锁操作伴随着加锁操作,那么下⾯我们先假设⼀个场景,即使线程A先抢到了锁,线程B阻塞等 待,那么当线程A执⾏完毕掉⽤了unlock⽅法后,如何唤醒线程B去继续抢锁的
在这里插入图片描述
现在,我们把视野再拉回来。我们在上⾯演示了多个线程的加锁操作对线程等待队列的影响。那 么,我们再来看⼀下这些线程分别调⽤解锁操作,这个队列中的等待线程⼜会是怎么的操作逻辑 呢?好,下⾯我们就先来看⼀下线程A执⾏unlock操作
在这里插入图片描述
线程B执⾏解锁操作
在这里插入图片描述
线程C执⾏解锁操作在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值