AQS源码解析---独占锁释放

        

目录

一 release

二 tryRelease

三 unparkSuccessor

 四 LockSupport.unpark


在梳理清楚独占锁获取后,独占锁的释放就相对简单了。锁的释放不涉及非公平,公平的说法,逻辑都是一样的。

一 release

        在demo代码中打断点,进入到unlock方法:

AQS源码解析---独占锁释放_第1张图片

图 1.1

        进入到release方法后,整个核心的逻辑就是tryRelease方法和unparkSuccessor方法,先来分析tryRelease方法。

AQS源码解析---独占锁释放_第2张图片

图 1.2

二 tryRelease

         先声明下,在执行tryRelease的时候设置state状态的时候,没有调用CAS而是直接设值,是因为能释放我自己的只有自己,不会存在竞争,相比于锁获取,就不需要CAS了。

AQS源码解析---独占锁释放_第3张图片

 图 2.1

        c = getState() - releases 表示将当前持有锁的线程个数减1,从上图知,这里的release的值为1。注意这里的c值可以大于0(重入的情况)。接下来通过

Thread.currentThread() != getExclusiveOwnerThread() 

判断释放锁的线程是不是当前线程,如果不是就抛出不合法的监视器异常。如果是继续往下走,当c=0时表示锁完全释放了,将free=true,且将当前持有锁的线程设置为null,表示没有线程持有该锁了。这部分代码逻辑很简单。

三 unparkSuccessor

         在锁成功释放会,满足if判断就会进入到unparkSuccessor。

AQS源码解析---独占锁释放_第4张图片

 图 3.1

         这里先获得head节点,head节点就哑巴节点,在独占锁获取一文中有讲,此时这个head节点可理解为当前获取锁的线程。注意这里进入到unparkSuccessor的条件是h!=null且h.waitStatus!=0.h!=null很好理解,那为什么是h.waitStatus!=0呢。在独占锁的释放文章中(AQS源码解析---独占锁获取),有讲到,waitStatus只有两处被设置状态,一个是新建节点(addWaiter)中,将一个新节点入队或初始化队列的时候,waitStatus的值赋值为0。还有一个地方时由需要被唤醒的节点将它前面第一个等待锁的前驱节点的waitStatus设置为SINGAL(-1)。如果head节点的waitStatus等于0,表明没有等待锁的节点,就没必要执行unparkSuccessor方法了。

     AQS源码解析---独占锁释放_第5张图片

 图 3.2

         先判断head节点(这里的node就是head节点)的ws是否小于0,小于0就直接设置为0。这是因为小于0表示后续有节点需要唤醒,先把我这个head节点设置为0,再执行后续操作。注意,独占锁的释放,这个ws的状态只能是0或-1,如果是-1(上诉有讲到原因),就表示有后继节点。需要唤醒。从debug也能看出,这个ws值为-1.

AQS源码解析---独占锁释放_第6张图片

 图 3.3

     然后取出后继节点,当后继节点为null或者waitStatus  > 0的时候,会执行一个倒叙遍历。waitStatus > 0 只能为 1,见waitStatus的状态:

AQS源码解析---独占锁释放_第7张图片

 图 3.4

     当后继节点为null或者waitStatus  > 0的时候,表示后继节点不知道啥原因放弃等待锁(中断等等)。从理论上来讲,应该继续找一个后继节点才是,而这里是进行了一个倒序遍历,原因在(AQS源码解析---独占锁获取)已经提到过,这里再回忆下:

AQS源码解析---独占锁释放_第8张图片

 图 3.5

       

        这里当t.waitStatus <= 0继续往前找,t.waitStatus = -1表示我的后继节点需要被我唤醒,t.waitStatus = 0表示没有后继节点需要我唤醒,我由我的前驱节点唤醒。waitStatus的状态在上诉也说了,在独占锁的获取时,这个值不是为0就是为-1。

        通过倒序遍历找到离head节点最近的一个需要被唤醒的节点,如果存在的话,就调用LockSupport.unpark方法。

AQS源码解析---独占锁释放_第9张图片

 图 3.6

 四 LockSupport.unpark

        在执行了LockSupport.unpark后就唤醒了等待的线程,在 (AQS源码解析---独占锁获取)中将到,线程在获取锁失败被挂起的地方时 parkAndCheckInterrupt。

AQS源码解析---独占锁释放_第10张图片

 图 3.7

         线程被唤醒后,执行Thread.interrupted()后返回。Thread.interrupted()是返回当前执行线程的中断状态并清楚中断标志。如果被中断过,就返回true。这里我们回到调用parkAndCheckInterrupt的上一步。

         AQS源码解析---独占锁释放_第11张图片

 图 3.8

        如果这里 parkAndCheckInterrupt返回的为true,就将interrupted设置为true。表示当前线程被中断过。注意,这里哪怕当前线程被中断过,在被唤醒后,也是继续执行这个for(;;)循环,继续进行强锁,抢到锁后从acquireQueued返回。回到上一步函数:

AQS源码解析---独占锁释放_第12张图片

 图 3.9

         如果,被中断过,就执行selfIntreeupt自我中断下。这里执行自我中断的原因,自己的理解,不一定对:

1.上诉讲到调用了Thread.interrupted(),这个是会清楚中断标志的,但是当线程被唤醒后,都没理这个中断,也没根据这个中断做出什么操作。就继续开始抢锁了。

2.当抢到锁后,我们需要将这个中断补上,这样才能知道线程是被中断过(可能被其他线程同通过unpark唤醒,或者是在等待锁过程中被中断等等)。

3.这里如果不在自我中断下的话,该线程压根就无法感知这个中断了,会丢失中断信息。

     补充说明,中断对线程是一个建议,中断线程不会影响线程的执行。我们可以根据这个中断标志来处理或不处理。但是这个中断建议需要保留,不要丢失,具体需要根据这个中断执行或不执行一些处理那是后面的是。

到此,独占锁的释放源码分析完毕了~~~

你可能感兴趣的:(多线程,AQS,java)