我们知道,AQS中最重要的两个方法就是acquire和release方法。我们本文来走读走读acquire的源码。
首先,tryAcquire是需要子类具体去实现,其作用就是设置state的值,如果设置成功,就代表获取资源,否则会进入下面的流程,也就是将当前线程封装到阻塞队列。
下面先来分析addWaiter方法。我们进入addWaiter方法看看。
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()方法的源码。
1、进入死循环。先获取 node 对象 prev 节点,如果该节点和 head 相等,说明是他的第二个节点,那么此时就可以尝试获取锁了。 如果获取锁成功,就设置当前节点为 head 节点(同时设置当前node的线程为null,prev为null),并设置他的 prev 节点的 next 节点为 null(帮助GC回收)。最后,返回等待过程中是否中断的布尔值。
2、如果上面的两个条件不成立,则调用shouldParkAfterFailedAcquire 方法和parkAndCheckInterrupt 方法。这两个方法的目的就是将当前线程挂起。然后等待被唤醒或者被中断。稍后,我们仔细查看这两个方法。
3、如果挂起后被当前线程唤醒,则再度循环,判断是该节点的 prev 节点是否是 head。一般来讲,当线程被唤醒,说明你可以获取锁了,也就是 head 节点完成了任务释放了锁。然后重复步骤 1。最后返回。
我们来看看shouldParkAfterFailedAcquire方法的源码。
这个代码比较简单,首先我们先看一下Node节点中定义的几种状态。
现在再来看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方法的源码。
该方法非常的简单,就是将当前线程挂起,等到有别的线程唤醒(通常是 head 节点的线程),然后返回当前线程是否是被中断了,注意,该方法会清除中断状态。
总结:
AQS中的acquire的源码其实还是比较简单的,读者可以对着源码再详细过一遍加深理解和印象。