我们知道,AQS中最重要的两个方法就是acquirerelease方法。我们本文来走读走读acquire的源码。

Java高并发系列之AQS中acquire源码解析_java

       首先,tryAcquire是需要子类具体去实现,其作用就是设置state的值,如果设置成功,就代表获取资源,否则会进入下面的流程,也就是将当前线程封装到阻塞队列。

     下面先来分析addWaiter方法。我们进入addWaiter方法看看。

Java高并发系列之AQS中acquire源码解析_java_02

1、首先,创建一个Node节点,我们知道这个Node节点是AQS的一个内部类。也是AQS阻塞队列(注:AQS阻塞队列是基于双向链表实现的)的基本类型,你可以想象成链表的Node节点。

2、由于AQS插入的顺序是在尾节点tail插入元素,取元素是在head节点进行。AQS初始化的时候,head和tail都指向null。因此这里Node pred首先指向尾节点。如果尾节点不为空则将当前创建的node节点前驱节点设置为pred。然后调用compareAndSetTail这个CAS操作将当前创建的node设置为tail节点,如果设置成功则将之前尾节点的pred后驱节点指向node。这里也就是说将新创建的node设置为新的tail节点,同时设置链表的指针。

3、如果尾节点为null,那么就会进入enq方法,这个方法就是AQS入队操作,其实也就是双向链表的操作,具体可以字节去看看源码。


下面来来看看acquireQueued()方法的源码。

Java高并发系列之AQS中acquire源码解析_java_03

1、进入死循环。先获取 node 对象 prev 节点,如果该节点和 head 相等,说明是他的第二个节点,那么此时就可以尝试获取锁了。 如果获取锁成功,就设置当前节点为 head 节点(同时设置当前node的线程为null,prev为null),并设置他的 prev 节点的 next 节点为 null(帮助GC回收)。最后,返回等待过程中是否中断的布尔值。

2、如果上面的两个条件不成立,则调用shouldParkAfterFailedAcquire 方法和parkAndCheckInterrupt 方法。这两个方法的目的就是将当前线程挂起。然后等待被唤醒或者被中断。稍后,我们仔细查看这两个方法。

3、如果挂起后被当前线程唤醒,则再度循环,判断是该节点的 prev 节点是否是 head。一般来讲,当线程被唤醒,说明你可以获取锁了,也就是 head 节点完成了任务释放了锁。然后重复步骤 1。最后返回。


我们来看看shouldParkAfterFailedAcquire方法的源码。

Java高并发系列之AQS中acquire源码解析_java_04

这个代码比较简单,首先我们先看一下Node节点中定义的几种状态。

Java高并发系列之AQS中acquire源码解析_java_05

       现在再来看shouldParkAfterFailedAcquire的源码。

1、获取去上一个节点的等待状态,如果状态是 SIGNAL,就直接返回 true,表示可以挂起并休息。

2、如果 waitStatus 大于 0, 则循环检查 prev 节点的 prev 的waitStatus,直到遇到一个状态不大于0。该字段有4个状态,分别是 CANCELLED = 1,SIGNAL = -1, CONDITION = -2, PROPAGATE = -3,也就是说,如果大于 0,就是取消状态。那么,往上找到那个不大于0的节点后怎么办?将当前节点指向那个节点的 next 节点,也就是说,那些大于0 状态的节点都失效这里,随时会被GC回收。

3、如果不大于0 也不是 -1,则将上一个节点的状态设置为有效, 也就是将Node的状态设置为SIGNAL。


     下面,我们再看看parkAndCheckInterrupt方法的源码。

Java高并发系列之AQS中acquire源码解析_java_06

        该方法非常的简单,就是将当前线程挂起,等到有别的线程唤醒(通常是 head 节点的线程),然后返回当前线程是否是被中断了,注意,该方法会清除中断状态。


总结:

      AQS中的acquire的源码其实还是比较简单的,读者可以对着源码再详细过一遍加深理解和印象。