由于AQS的源码太过凝练,而且有很多分支比如取消排队、等待条件等,如果把所有的分支在一篇文章的写完可能会看懵,所以这篇文章主要是从正常流程先走一遍,重点不在取消排队等分支,之后会专门写一篇取消排队和等待条件的分支逻辑。读源码千万别在每个代码分支中来回游走,先按一个正常的分支把流程看明白,之后再去重点关注其他分支,各个击破。我相信看完正常流程,你再去分析其他分支会更加得心应手。本篇将主要方法名都做了目录索引,查看时可通过目录快速跳到指定方法的逻辑。
执行流程AQS的执行流程大体为当线程获取锁失败时,会加入到等待队列中,在等待队列中的线程会按照从头至尾的顺序依次再去尝试获取锁执行。
当线程获取锁后如果还需要等待特定的条件才能执行,那么线程就加入到条件队列排队,当等待的条件到来时再从条件队列中按照从头至尾的顺序加入到等待队列中,然后再按照等待队列的执行流程去获取锁。所以AQS最核心的数据结构其实就两个队列,等待队列和条件队列,然后再加上一个获取锁的同步状态。
AQS数据结构AQS最核心的数据结构就三个
等待队列
源码中head和tail为等待队列的头尾节点,在通过前后指向则构成了等待队列,为双向链表,学名为CLH队列。
条件队列
ConditionObject中的firstWaiter和lastWaiter为等待队列的头尾节点,然后通过next指向构成了条件队列,是个单向链表。
同步状态
state为同步状态,通过CAS操作来实现获取锁的操作。
public abstract class AbstractQueuedSynchronizer{ /** * 等待队列的头节点 */ private transient volatile Node head; /** * 等待队列的尾节点 */ private transient volatile Node tail; /** * 同步状态 */ private volatile int state; public class ConditionObject implements Condition, java.io.Serializable { /** 条件队列的头节点 */ private transient Node firstWaiter; /** 条件队列的尾节点 */ private transient Node lastWaiter; } } Node节点两个队列中的节点都是通过AQS中内部类Node来实现的。主要字段:
waitStatus
当前节点的状态,具体看源码列出的注释。很重要,之后会在源码中讲解。
Node prev
等待队列节点指向的前置节点
Node next
待队列节点指向的后置节点
Node nextWaiter
条件队列中节点指向的后置节点
Thread thread
当前节点持有的线程
static final class Node { /** */ static final Node SHARED = new Node(); /** */ static final Node EXCLUSIVE = null; /** 标明当前节点线程取消排队 */ static final int CANCELLED = 1; /** 标明该节点的后置节点需要自己去唤醒 */ static final int SIGNAL = -1; /** 标明当前节点在等待某个条件,此时节点在条件队列中 */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; /** * 等待状态,值对于上面的四个常量 */ volatile int waitStatus; /** * 等待队列节点指向的前置节点 */ volatile Node prev; /** * 等待队列节点指向的后置节点 */ volatile Node next; /** * 当前节点持有的线程 */ volatile Thread thread; /** * 条件队列中节点指向的后置节点 */ Node nextWaiter; 加锁上面说明的数据结构我们先大致有个印象,现在通过加锁来一步步说明下具体的流程,上篇文章JUC并发编程基石AQS之结构篇,我们知道了AQS加锁代码执行的是acquire方法,那么我们从这个方法说起,从源码中看出执行流程为:tryAcquire——>addWaiter——>acquireQueued
tryAcquire为自己实现的具体加锁逻辑,当加锁失败时返回false,则会执行addWaiter,将线程加入到等待队列中,Node.EXCLUSIVE为独占锁的模式,即同时只能有一个线程获取锁去执行。
例子说明
首先假设有四个线程t0-t4调用tryAcquire获取锁,t0线程为天选之子获取到了锁,则t1-t4线程接着去执行addWaiter。
acquire public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } addWaiter分支1