源码分析:AQS源码

在开始这篇源码之前,最好先看下转载整理的这篇文章,有很多值得学习的地方。AQS是用来构建锁或者其他同步组件的基础框架。总体来说,它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步状态进行修改,确保状态的改变是安全的。通过内置的 FIFO (First In First Out)队列来完成资源获取线程的排队工作

1、AQS 同步和 synchronized 关键字同步

在介绍 AQS 的使用之前,需要首先说明一点,AQS 同步和 synchronized 关键字同步(以下简称 synchronized 同步)是采用的两种不同的机制。首先看下 synchronized 同步,synchronized 关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码需要关联到一个监视对象,当线程执行 monitorenter 指令时,需要首先获得获得监视对象的锁,这里监视对象锁就是进入同步块的凭证,只有获得了凭证才可以进入同步块,当线程离开同步块时,会执行 monitorexit 指令,释放对象锁。

在 AQS 同步中,使用一个 int 类型的变量 state 来表示当前同步块的状态。以独占式同步(一次只能有一个线程进入同步块)为例,state 的有效值有两个 0 和 1,其中 0 表示当前同步块中没有线程,1 表示同步块中已经有线程在执行。当线程要进入同步块时,需要首先判断 state 的值是否为 0,假设为 0,会尝试将 state 修改为 1,只有修改成功了之后,线程才可以进入同步块。注意上面提到的两个条件:

A、state 为 0,证明当前同步块中没有线程在执行,所以当前线程可以尝试获得进入同步块的凭证,而这里的凭证就是是否成功将 state 修改为 1
B、成功将 state 修改为 1,通过使用 CAS 操作,我们可以确保即便有多个线程同时修改 state,也只有一个线程会修改成功。

当线程离开同步块时,会修改 state 的值,将其设为 0,并唤醒等待的线程。所以在 AQS 同步中,我们说线程获得了锁,实际上是指线程成功修改了状态变量 state,而线程释放了锁,是指线程将状态变量置为了可修改的状态(在独占式同步中就是置为了 0),让其他线程可以再次尝试修改状态变量。在下面的表述中,我们说线程获得和释放了锁,就是上述含义, 这与 synchronized 同步中说的获得和释放锁的含义不同,需要区别理解。

2、AQS框架在实现者中的应用

AQS负责管理同步器类中的状态,它管理了一个整数状态信息,可以通过getState,setState及compareAndSetState等方法进行操作。这个整数状态的意义由子类来赋予,如ReentrantLock中该状态值表示所有者线程已经重复获取该锁的次数;Semaphore中该状态值表示剩余的许可数量。AQS暴露给子类一些方法实现(如tryAcquire,tryRelease), 获取操作通常是依赖state状态的,当状态允许时获取成功否则加入等待队列一直等待到允许的状态发生时重新获取,例如,Semaphore中当state(即许可数量)小于等于0时,获取不能成功。释放操作通过会修改状态并判断是否能让其他等待的线程能够重新获取,例如ReentrantLock中释放会减少重入次数并检查重入次数是否达到了0,达到0说明已经解锁,这时会通知其他等待线程重新获取。所以AQS的acquire和release会通过组合应用到不同的同步器实现中实现不同的语义,如Reentrant.lock, Semaphore.acquire, CountDownLatch.await, FutureTask.get等state以及队列的操作都是采用了volatile + CAS + 自旋的操作方式,采用的是乐观锁的概念

另外AQS还提供其他功能,如非阻塞的获取tryAcquire, 带有超时时间的获取,可以中断的获取等。AQS可以根据具体的场景提供exclusive模式和shared模式,在exclusive模式下,同一时刻最多只能有一个线程能够处于成功获取的状态,排他锁是一个exclusive模式的例子,shared模式则可以多个线程一起获取成功,如多个许可的Semaphore。另外在java.util.concurrent包中还定义了Condition接口,用来提供监视器风格的等待通知操作,可以替换Object中基于synchronized、监视器锁的wait和notify机制。

3、AQS框架原理图

图来自

AQS 依赖内部的同步队列(一个 FIFO的双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把队列中第一个等待节点线程唤醒,使其再次尝试获取同步状态。

4、AQS相关类图关系

ConditionObject和Node都是AQS的内部类,AbstractOwnableSynchronizer是AQS的父类,AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的父类,该类只有一个Thread类型的成员变量exclusiveOwnerThread,用来保存是哪个线程获得了独占锁或者写锁,当该值为null时,表示没有线程获得独占锁或者写锁。 AbstractOwnableSynchronizer定义了基础的创建锁和相关同步器的方法,但其本身并不管理维护这些信息,而是交由子类去实现。关于LockSupport,可以看这篇文章。AQS内部方法可以看这些:

class AbstractQueuedSynchronizer {
    static final class Node{}
    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    private Node enq()
    private void setHead(Node node)
    private void unparkSuccessor(Node node)
    private void doReleaseShared()
    final boolean acquireQueued(final Node node, int arg) 
    private void doAcquireInterruptibly(int arg)
    private boolean doAcquireNanos(int arg, long nanosTimeout)
    private void doAcquireShared(int arg) 
    private void doAcquireSharedInterruptibly(int arg)
    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
    // 需要子类去实现的方法
    protected boolean tryAcquire(int arg)
    protected boolean tryRelease(int arg) 
    protected int tryAcquireShared(int arg) 
    protected boolean tryReleaseShared(int arg)
    // 对外提供的public方法
    public final void acquire()
    public final void acquireInterruptibly(int arg)()
    public final boolean tryAcquireNanos()
    public final boolean release(int arg)()
    public final void acquireShared(int arg)()
    public final void acquireSharedInterruptibly(int arg)()
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)()
    public final boolean releaseShared(int arg) ()
    ...
}

aos:

  /**
   * AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的父类,该类只有一个Thread类型的成员变量exclusiveOwnerThread,
   * 用来保存是哪个线程获得了独占锁或者写锁,当该值为null时,表示没有线程获得独占锁或者写锁。
   * AbstractOwnableSynchronizer定义了基础的创建锁和相关同步器的方法,但其本身并不管理维护这些信息,而是交由子类去实现。
   */
  public abstract class AbstractOwnableSynchronizer
      implements java.io.Serializable {

    /**
     * 唯一的变量就是exclusiveOwnerThread,而且还是transient
     */
    private static final long serialVersionUID = 3737899427754241961L;

    /**
     * 子类使用
     */
    protected AbstractOwnableSynchronizer() { }

    /**
     * https://www.cnblogs.com/woshimrf/p/java-serialize.html
     * transient修饰的字段不能被序列化,至于静态字段,这里不做测试,但要清楚。静态字段只和class类相关,和实例无关。
     * 而序列化是针对实例的,所以无所谓对比内容变化。很多源码里边有用transient,比如ArrayList,为什么呢?
     * 因为数组元素有很多空余空间,对我们来说不需要序列化。通过这样自定义,把需要的元素序列化,可以节省空间。
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置获得独占锁的线程,null意味着没有线程有访问权限
     * 此方法不会强制执行任何同步
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
      exclusiveOwnerThread = thread;
    }

    /**
     * 返回最后set的那个线程
     */
    protected final Thread getExclusiveOwnerThread() {
      return exclusiveOwnerThread;
    }
  }

5、AQS实现逻辑

在李老头的论文里提到了同步器的伪代码:

// acquire操作如下:
while (synchronization state does not allow acquire) {
    enqueue current thread if not already queued;
    possibly block current thread;
}
dequeue current thread if it was queued;

//release操作如下:
update synchronization state;
if (state may permit a blocked thread to acquire){
    unblock one or more queued threads;
}

为了实现上述操作,需要下面三个基本组件的相互协作:

A、同步状态的原子性管理。
B、等待队列的管理。
C、线程的阻塞与解除阻塞。

AQS内部内部定义了一个32位整型的state变量用于保存同步状态:

/**
 * The synchronization state.
 */
private volatile int state;

// 获取state
protected final int getState() {
    return state;
}

// 直接覆盖设置state
protected final void setState(int newState) {
    state = newState;
}

// CAS设置state
protected final boolean compareAndSetState(int expect, int update) {
    return STATE.compareAndSet(this, expect, update);
}

同步状态state在不同的实现中可以有不同的作用或者表示意义,它可以代表资源数、锁状态等等。AQS是基于CLH锁变型实现的,CLH锁即Craig, Landin, and Hagersten (CLH) locks,因为它底层是基于队列实现,一般也称为CLH队列锁。CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。从实现上看,CLH锁是一种自旋锁,能确保无饥饿性,提供先来先服务的公平性。,先看一下CLH锁的基本实现:

public class ClhSpinLock implements Lock{
    private final ThreadLocal<Node> prev;
    private final ThreadLocal<Node> node;
    private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());

    public ClhSpinLock() {
        this.node = new ThreadLocal<Node>() {
            protected Node initialValue() {
                return new Node();
            }
        };

        this.prev = new ThreadLocal<Node>() {
            protected Node initialValue() {
                return null;
            }
        };
    }

    /**
     * 1.初始状态 tail指向一个node(head)节点 
     * +------+ 
     * | head | <---- tail 
     * +------+
     * 
     * 2.lock-thread加入等待队列: tail指向新的Node,同时Prev指向tail之前指向的节点
     * +----------+
     * | Thread-A |
     * | := Node  | <---- tail
     * | := Prev  | -----> +------+
     * +----------+        | head |
     *                     +------+ 
     * 
     *             +----------+            +----------+
     *             | Thread-B |            | Thread-A |
     * tail ---->  | := Node  |     -->    | := Node  | 
     *             | := Prev  | ----|      | := Prev  | ----->  +------+
     *             +----------+            +----------+         | head |
     *                                                          +------+ 
     * 3.寻找当前node的prev-node然后开始自旋
     * 
     */
    public void lock() {
        final Node node = this.node.get();
        node.locked = true;
        Node pred = this.tail.getAndSet(node);
        this.prev.set(pred);
        // 自旋
        while (pred.locked);
    }

    public void unlock() {
        final Node node = this.node.get();
        node.locked = false;
        this.node.set(this.prev.get());
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public boolean tryLock() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public Condition newCondition() {
        // TODO Auto-generated method stub
        return null;
    }
    
    private static class Node {
        private volatile boolean locked;
    }
}

上面是一个简单的CLH队列锁的实现,内部类Node只使用了一个简单的布尔值locked属性记录了每个线程的状态,如果该属性为true,则相应的线程要么已经获取到锁,要么正在等待锁,如果该属性为false,则相应的线程已经释放了锁。新来的想要获取锁的线程必须对tail属性调用getAndSet()方法,使得自身成为队列的尾部,同时得到一个指向前驱节点的引用pred,最后线程所在节点在其前驱节点的locked属性上自旋,值得前驱节点释放锁。上面的实现是无法运行的,因为一旦自旋就会进入死循环导致CPU飙升,可以尝试使用下面将要提到的LockSupport进行改造。

CLH队列锁本质是使用队列(实际上是单向链表)存放等待获取锁的线程,等待的线程总是在其所在节点的前驱节点的状态上自旋,直到前驱节点释放资源。从实际来看,过度自旋带来的CPU性能损耗比较大,并不是理想的线程等待队列实现。

基于原始的CLH队列锁中提供的等待队列的基本原理,AQS实现一种了CLH锁队列的变体(variant)。AQS类的protected修饰的构造函数里面有一大段注释用于说明AQS实现的等待队列的细节事项,这里列举几点重要的:

A、AQS实现的等待队列没有直接使用CLH锁队列,但是参考了其设计思路,等待节点会保存前驱节点中线程的信息,内部也会维护一个控制线程阻塞的状态值。
B、每个节点都设计为一个持有单独的等待线程并且”带有具体的通知方式”的监视器,这里所谓通知方式就是自定义唤醒阻塞线程的方式而已。
C、一个线程是等待队列中的第一个等待节点的持有线程会尝试获取锁,但是并不意味着它一定能够获取锁成功(这里的意思是存在公平和非公平的实现),获取失败就要重新等待。
D、等待队列中的节点通过prev属性连接前驱节点,通过next属性连接后继节点,简单来说,就是双向链表的设计。
E、CLH队列本应该需要一个虚拟的头节点,但是在AQS中没有直接提供虚拟的头节点,而是延迟到第一次竞争出现的时候懒创建虚拟的头节点(其实也会创建尾节点,初始化时头尾节点是同一个节点)。
F、Condition(条件)等待队列中的阻塞线程使用的是相同的Node结构,但是提供了另一个链表用来存放,Condition等待队列的实现比非Condition等待队列复杂。

线程的阻塞和唤醒在JDK1.5之前,一般只能依赖于Object类提供的wait()、notify()和notifyAll()方法,它们都是JNI方法,由JVM提供实现,并且它们必须运行在获取监视器锁的代码块内(synchronized代码块中),这个局限性先不谈性能上的问题,代码的简洁性和灵活性是比较低的。JDK1.5引入了LockSupport类,底层是基于Unsafe类的park()和unpark()方法,提供了线程阻塞和唤醒的功能,它的机制有点像只有一个允许使用资源的信号量java.util.concurrent.Semaphore,也就是一个线程只能通过park()方法阻塞一次,只能调用unpark()方法解除调用阻塞一次,线程就会唤醒(多次调用unpark()方法也只会唤醒一次),可以想象是内部维护了一个0-1的计数器。

这里先重点分析一下AQS中等待队列的节点AQS$Node的源码:

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;
   // 传播状态
   static final int PROPAGATE = -3;
   // 等待状态,初始值为0,其他可选值是上面的4个值
   volatile int waitStatus;
   // 当前节点前驱节点的引用
   volatile Node prev;
   // 当前节点后继节点的引用
   volatile Node next;
   // 当前节点持有的线程,可能是阻塞中等待唤醒的线程
   volatile Thread thread;
   // 下一个等待节点
   Node nextWaiter;
   // 当前操作的节点是否处于共享模式
   final boolean isShared() {
      return nextWaiter == SHARED;
   }
   // 获取当前节点的前驱节点,确保前驱节点必须存在,否则抛出NPE  
   final Node predecessor() {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    
    // 空节点,主要是首次创建队列的时候创建的头和尾节点使用
    Node() {}

    // 设置下一个等待节点,设置持有线程为当前线程
    Node(Node nextWaiter) {
        this.nextWaiter = nextWaiter;
        THREAD.set(this, Thread.currentThread());
    }

    // 设置waitStatus,设置持有线程为当前线程
    Node(int waitStatus) {
        WAITSTATUS.set(this, waitStatus);
        THREAD.set(this, Thread.currentThread());
    }

    // CAS更新waitStatus  
    final boolean compareAndSetWaitStatus(int expect, int update) {
        return WAITSTATUS.compareAndSet(this, expect, update);
    }
    // CAS设置后继节点
    final boolean compareAndSetNext(Node expect, Node update) {
        return NEXT.compareAndSet(this, expect, update);
    }
    // 设置前驱节点
    final void setPrevRelaxed(Node p) {
        PREV.set(this, p);
    }

    // 下面是变量句柄的实现,在VarHandle出现之前使用的是Unsafe,其实底层还是照样使用Unsafe
    private static final VarHandle NEXT;
    private static final VarHandle PREV;
    private static final VarHandle THREAD;
    private static final VarHandle WAITSTATUS;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            NEXT = l.findVarHandle(Node.class, "next", Node.class);
            PREV = l.findVarHandle(Node.class, "prev", Node.class);
            THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
            WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }	  
}

其中,变量句柄(VarHandle)是JDK9引用的新特性,其实底层依赖的还是Unsafe的方法,总体和JDK8的实现是基本一致。这里需要关注一下Node里面的几个属性:

waitStatus:当前Node实例的等待状态,可选值有5个。

    A、初始值整数0:当前节点如果不指定初始化状态值,默认值就是0,侧面说明节点正在等待队列中处于等待状态。
    B、Node#CANCELLED整数值1:表示当前节点实例因为超时或者线程中断而被取消,等待中的节点永远不会处于此状态,被取消的节点中的线程实例不会阻塞。
    C、Node#SIGNAL整数值-1:表示当前节点的后继节点是(或即将是)阻塞的(通过park),当它释放或取消时,当前节点必须unpark它的后继节点。
    D、Node#CONDITION整数值-2:表示当前节点是条件队列中的一个节点,当它转换为同步队列中的节点的时候,状态会被重新设置为0。
    E、Node#PROPAGATE整数值-3:此状态值通常只设置到调用了doReleaseShared()方法的头节点,确保releaseShared()方法的调用可以传播到其他的所有节点,简单理解就是共享模式下节点释放的传递标记。
prev、next:当前Node实例的前驱节点引用和后继节点引用。
thread:当前Node实例持有的线程实例引用。
nextWaiter:这个值是一个比较容易令人生疑的值,虽然表面上它称为”下一个等待的节点”,但是实际上它有三种取值的情况。
    A、值为静态实例Node.EXCLUSIVE(也就是null),代表当前的Node实例是独占模式。
    B、值为静态实例Node.SHARED,代表当前的Node实例是共享模式。
    C、值为非Node.EXCLUSIVE和Node.SHARED的其他节点实例,代表Condition等待队列中当前节点的下一个等待节点。

实际上,AQS中一共存在两种等待队列,其中一种是普通的同步等待队列,这里命名为Sync-Queue,另一种是基于Sync-Queue实现的条件等待队列,这里命名为Condition-Queue。前面已经介绍完AQS的同步等待队列节点类,下面重点分析一下同步等待队列的相关源码,下文的Sync队列、同步队列和同步等待队列是同一个东西。首先,我们通过分析Node节点得知Sync队列一定是双向链表,AQS中有两个瞬时成员变量用来存放头节点和尾节点:

// 头节点引用
private transient volatile Node head;
// 尾节点引用
private transient volatile Node tail;

// 变量句柄相关,用于CAS操作头尾节点
private static final VarHandle STATE;
private static final VarHandle HEAD;
private static final VarHandle TAIL;

static {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
        HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class);
        TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class);
    } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
    }
    // 确保LockSupport类已经初始化 - 这里应该是为了修复之前一个因为LockSupport未初始化导致的BUG
    Class<?> ensureLoaded = LockSupport.class;
}

// 初始化同步队列,注意初始化同步队列的时候,头尾节点都是指向同一个新的Node实例
private final void initializeSyncQueue() {
    Node h;
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

// CAS设置同步队列的尾节点
private final boolean compareAndSetTail(Node expect, Node update) {
    return TAIL.compareAndSet(this, expect, update);
}

// 设置头节点,重点注意这里:传入的节点设置成头节点之后,前驱节点和持有的线程会置为null,这是因为:
// 1.头节点一定没有前驱节点。
// 2.当节点被设置为头节点,它所在的线程一定是已经解除了阻塞。
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

当前线程加入同步等待队列和同步等待队列的初始化是同一个方法,前文提到过:同步等待队列的初始化会延迟到第一次可能出现竞争的情况,这是为了避免无谓的资源浪费,具体方法是addWaiter(Node mode):

// 添加等待节点到同步等待队列,实际上初始化队列也是这个方法完成的
private Node addWaiter(Node mode) {
    // 基于当前线程创建一个新节点,节点的模式由调用者决定
    Node node = new Node(mode);
    for (;;) {
        Node oldTail = tail;
       // 尾节点不为空说明队列已经初始化过,则把新节点加入到链表中,作为新的尾节点,建立和前驱节点的关联关系
        if (oldTail != null) {
            node.setPrevRelaxed(oldTail);
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
        } else {
	    // 尾节点为空说明队列尚未初始化过,进行一次初始化操作
            initializeSyncQueue();
        }
    }
}

在首次调用addWaiter()方法,死循环至少执行两轮再跳出,因为同步队列必须初始化完成后(第一轮循环),然后再把当前线程所在的新节点实例添加到等待队列中再返回(第二轮循环)当前的节点,这里需要注意的是新加入同步等待队列的节点一定是添加到队列的尾部并且会更新AQS中的tail属性为最新入队的节点实例。

Condition-Queue

前面已经相对详细地介绍过同步等待队列,在AQS中还存在另外一种相对特殊和复杂的等待队列-条件等待队列。介绍条件等待队列之前,要先介绍java.util.concurrent.locks.Condition接口。

public interface Condition {
    
    // 当前线程进入等待状态直到被唤醒或者中断
    void await() throws InterruptedException;
    // 当前线程进入等待状态,不响应中断,阻塞直到被唤醒
    void awaitUninterruptibly();
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒单个阻塞线程
    void signal();
    // 唤醒所有阻塞线程
    void signalAll();
}

Condition可以理解为Object中的wait()、notify()和notifyAll()的替代品,因为Object中的相应方法是JNI(Native)方法,由JVM实现,对使用者而言并不是十分友好(可能需要感知JVM的源码实现),而Condition是基于数据结构和相应算法实现对应的功能,我们可以从源码上分析其实现。

Condition的实现类是AQS的公有内部类ConditionObject。ConditionObject提供的入队列方法如下:

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */ - 条件队列的第一个节点
    private transient Node firstWaiter;
    /** Last node of condition queue. */ - 条件队列的最后一个节点
    private transient Node lastWaiter;
    // 公有构造函数
    public ConditionObject() { }
    // 添加条件等待节点
    private Node addConditionWaiter() {
        // 这里做一次判断,当前线程必须步入此同步器实例
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        // 临时节点t赋值为lastWaiter引用
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        // 最后一个节点不为条件等待状态,则是取消状态
        if (t != null && t.waitStatus != Node.CONDITION) {
            // 解除所有取消等待的节点的连接
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        // 基于当前线程新建立一个条件等待类型的节点
        Node node = new Node(Node.CONDITION);
        // 首次创建Condition的时候,最后一个节点临时引用t为null,则把第一个节点置为新建的节点
        if (t == null)
            firstWaiter = node;
        else
            // 已经存在第一个节点,则通过nextWaiter连接新的节点
            t.nextWaiter = node;
        // 最后一个节点的引用更新为新节点的引用    
        lastWaiter = node;
        return node;
    } 
    // 从条件等待队列解除所有取消等待的节点的连接,其实就是所有取消节点移除的操作,涉及到双向链表的断链操作、第一个和最后一个节点的引用更新
    private void unlinkCancelledWaiters() {
        Node t = firstWaiter;
        Node trail = null;
        while (t != null) {
            Node next = t.nextWaiter;
            // 注意这里等待状态的判断
            if (t.waitStatus != Node.CONDITION) {
                t.nextWaiter = null;
                if (trail == null)
                    firstWaiter = next;
                else
                    trail.nextWaiter = next;
                if (next == null)
                    lastWaiter = trail;
            }
            else
                trail = t;
            t = next;
        }
    } 
    // 当前同步器实例持有的线程是否当前线程(currentThread())
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    } 

// 暂时不分析其他方法             
}

AQS同步器如果使用独占(EXCLUSIVE)模式,那么意味着同一个时刻,只有节点所在一个线程获取(acuqire)原子状态status成功,此时该线程可以从阻塞状态解除继续运行,而同步等待队列中的其他节点持有的线程依然处于阻塞状态。独占模式同步器的功能主要由下面的四个方法提供:

    1、acquire(int arg);申请获取arg个原子状态status(申请成功可以简单理解为status = status - arg)。
    2、acquireInterruptibly(int arg):申请获取arg个原子状态status,响应线程中断。
    3、tryAcquireNanos(int arg, long nanosTimeout):申请获取arg个原子状态status,带超时的版本。
    4、release(int arg):释放arg个原子状态status(释放成功可以简单理解为status = status + arg)。
独占模式下,AQS同步器实例初始化时候传入的status值,可以简单理解为”允许申请的资源数量的上限值”,下面的acquire类型的方法暂时称为”获取资源”,而release方法暂时称为”释放资源”。接着我们分析前面提到的四个方法的源码,先看acquire(int arg):

public final void acquire(int arg) {
    // 获取资源成功或者新增一个独占类型节点到同步等待队列成功则直接返回,否则中断当前线程
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 此方法必须又子类覆盖,用于决定是否获取资源成功
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 中断当前线程
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

// 不可中断的独占模式下,同步等待队列中的线程获取资源的方法
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            // 获取新入队节点的前驱节点
            final Node p = node.predecessor();
            // 前驱节点为头节点并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquire尝试获取资源,除非阻塞或者跳出循环
            if (p == head && tryAcquire(arg)) {
                // 设置新入队节点为头节点,原来的节点会从队列中断开
                setHead(node);
                p.next = null; // help GC
                return interrupted;   // <== 注意,这个位置是跳出死循环的唯一位置
            }
            // 判断是否需要阻塞当前获取资源失败的节点中持有的线程
            if (shouldParkAfterFailedAcquire(p, node))
                // 阻塞当前线程,如果被唤醒则返回并清空线程的中断标记
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}

/**
 * 检查并且更新获取资源失败的节点的状态,返回值决定线程是否需要被阻塞。
 * 这个方法是所有循环获取资源方法中信号控制的主要方法
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 这里记住ws是当前处理节点的前驱节点的等待状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驱节点状态设置成Node.SIGNAL成功,等待被release调用释放,后继节点可以安全地进入阻塞状态
        return true;
    if (ws > 0) {
        // ws大于0只有一种情况Node.CANCELLED,说明前驱节点已经取消获取资源,
        // 这个时候会把所有这类型取消的前驱节点移除,找到一个非取消的节点重新通过next引用连接当前节点
        do {
           node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 其他等待状态直接修改前驱节点等待状态为Node.SIGNAL
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

// 阻塞当前线程,获取并且重置线程的中断标记位
private final boolean parkAndCheckInterrupt() {
    // 这个就是阻塞线程的实现,依赖Unsafe的API
    LockSupport.park(this);
    return Thread.interrupted();
}

流程:

接着分析一下release(int arg)的实现:

// 释放资源
public final boolean release(int arg) {
    // 尝试释放资源
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 尝试释放资源,独占模式下,尝试通过重新设置status的值从而实现释放资源的功能
// 这个方法必须由子类实现
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 解除传入节点(一般是头节点)的第一个后继节点的阻塞状态,当前处理节点的等待状态会被CAS更新为0
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    // 当前处理的节点(一般是头节点)状态小于0则直接CAS更新为0
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 如果节点的第一个后继节点为null或者等待状态大于0(取消),则从等待队列的尾节点向前遍历,
        // 找到最后一个不为null,并且等待状态小于等于0的节点
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    // 解除上面的搜索到的节点的阻塞状态
    if (s != null)
        LockSupport.unpark(s.thread);
}

接着我们可以看acquire()的响应中断版本和带超时的版本。先看acquireInterruptibly(int arg):

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
    // 获取并且清空线程中断标记位,如果是中断状态则直接抛InterruptedException异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果获取资源失败
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

// 独占模式下响应中断的获取资源方法
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    // 基于当前线程新增一个独占的Node节点进入同步等待队列中
    final Node node = addWaiter(Node.EXCLUSIVE);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return;
            }
            // 获取资源失败进入阻塞状态
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    // 解除阻塞后直接抛出InterruptedException异常
                    throw new InterruptedException();
            }
         } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
    }
}

doAcquireInterruptibly(int arg)方法和acquire(int arg)类似,最大的不同点在于阻塞线程解除阻塞后并不是正常继续运行,而是直接抛出InterruptedException异常。最后看tryAcquireNanos(int arg, long nanosTimeout)的实现:

// 独占模式下尝试在指定超时时间内获取资源,响应线程中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

// 独占模式下带超时时间限制的获取资源方法
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    // 超时期限小于0纳秒,快速失败
    if (nanosTimeout <= 0L)
        return false;
    // 超时的最终期限是当前系统时钟纳秒+外部指定的nanosTimeout增量
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return true;
            }
            // 计算出剩余的超时时间
            nanosTimeout = deadline - System.nanoTime();
            // 剩余超时时间小于0说明已经超时则取消获取
            if (nanosTimeout <= 0L) {
                cancelAcquire(node);
                return false;
            }
            // 这里会判断剩余超时时间大于1000纳秒的时候才会进行带超时期限的线程阻塞,否则会进入下一轮获取尝试
            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                    LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
            }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

tryAcquireNanos(int arg, long nanosTimeout)其实和doAcquireInterruptibly(int arg)类似,它们都响应线程中断,不过tryAcquireNanos()在获取资源的每一轮循环尝试都会计算剩余可用的超时时间,只有同时满足获取失败需要阻塞并且剩余超时时间大于SPIN_FOR_TIMEOUT_THRESHOLD(1000纳秒)的情况下才会进行阻塞。

独占模式的同步器的一个显著特点就是:头节点的第一个有效(非取消)的后继节点,总是尝试获取资源,一旦获取资源成功就会解除阻塞并且晋升为头节点,原来所在节点会移除出同步等待队列,原来的队列长度就会减少1,然后头结点的第一个有效的后继节点继续开始竞争资源。

共享模式
共享(SHARED)模式中的”共享”的含义是:同一个时刻,如果有一个节点所在线程获取(acuqire)原子状态status成功,那么它会解除阻塞被唤醒,并且会把唤醒状态传播到所有的后继节点(换言之就是唤醒整个同步等待队列中的所有节点)。共享模式同步器的功能主要由下面的四个方法提供:

    1、acquireShared(int arg);申请获取arg个原子状态status(申请成功可以简单理解为status = status - arg)。
    2、acquireSharedInterruptibly(int arg):申请获取arg个原子状态status,响应线程中断。
    3、tryAcquireSharedNanos(int arg, long nanosTimeout):申请获取arg个原子状态status,带超时的版本。
    4、releaseShared(int arg):释放arg个原子状态status(释放成功可以简单理解为status = status + arg)。
先看acquireShared(int arg)的源码:

// 共享模式下获取资源
public final void acquireShared(int arg) {
    // 注意tryAcquireShared方法值为整型,只有小于0的时候才会加入同步等待队列
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

// 共享模式下尝试获取资源,此方法需要由子类覆盖
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

// 共享模式下获取资源和处理同步等待队列的方法
private void doAcquireShared(int arg) {
    // 基于当前线程新建一个标记为共享的新节点
    final Node node = addWaiter(Node.SHARED);
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            // 如果当前节点的前驱节点是头节点
            if (p == head) {
                // 每一轮循环都会调用tryAcquireShared尝试获取资源,除非阻塞或者跳出循环
                int r = tryAcquireShared(arg);
                if (r >= 0) {  // <= tryAcquireShared方法>=0说明直资源获取成功
                    // 设置头结点,并且传播获取资源成功的状态,这个方法的作用是确保唤醒状态传播到所有的后继节点
                    // 然后任意一个节点晋升为头节点都会唤醒其第一个有效的后继节点,起到一个链式释放和解除阻塞的动作
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return;
                }
            }
            // 判断获取资源失败是否需要阻塞,这里会把前驱节点的等待状态CAS更新为Node.SIGNAL
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    } finally {
        if (interrupted)
            selfInterrupt();
    }
}

// 设置同步等待队列的头节点,判断当前处理的节点的后继节点是否共享模式的节点,如果共享模式的节点,
// propagate大于0或者节点的waitStatus为PROPAGATE则进行共享模式下的释放资源
private void setHeadAndPropagate(Node node, int propagate) {
    // h为头节点的中间变量
    Node h = head;
    // 设置当前处理节点为头节点
    setHead(node);
    // 这个判断条件比较复杂:入参propagate大于0 || 头节点为null || 头节点的状态为非取消 || 再次获取头节点为null || 再次获取头节点不为取消
    if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 当前节点(其实已经成为头节点)的第一个后继节点为null或者是共享模式的节点
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

// Release action for shared mode:共享模式下的释放资源动作
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        // 头节点不为null并且不为尾节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果头节点等待状态为SIGNAL(-1)则CAS更新它为0,更新成功后唤醒和解除其后继节点的阻塞
            if (ws == Node.SIGNAL) {
                if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                    continue;
                // 唤醒头节点的后继节点
                unparkSuccessor(h);
            }
            // 如果头节点的等待状态为0,则CAS更新它为PROPAGATE(-3)
            else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                continue;
            }
        // 头节点没有变更,则跳出循环
        if (h == head)
            break;
    }
}

其实代码的实现和独占模式有很多类似的地方,一个很大的不同点是:共享模式同步器当节点获取资源成功晋升为头节点之后,它会把自身的等待状态通过CAS更新为Node.PROPAGATE,下一个加入等待队列的新节点会把头节点的等待状态值更新回Node.SIGNAL,标记后继节点处于可以被唤醒的状态,如果遇上资源释放,那么这个阻塞的节点就能被唤醒解除阻塞。然后进行资源释放确保tryAcquireShared(int arg)总是返回大于0的值,看releaseShared(int arg)的源码:

// 共享模式下释放资源
public final boolean releaseShared(int arg) {
    // 尝试释放资源成功则调用前面分析过的doReleaseShared以传播唤醒状态和unpark头节点的后继节点
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

// 共享模式下尝试释放资源,必须由子类覆盖
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

releaseShared(int arg)就是在tryReleaseShared(int arg)调用返回true的情况下主动调用一次doReleaseShared()从而基于头节点传播唤醒状态和unpark头节点的后继节点。接着看acquireSharedInterruptibly(int arg)的源码实现:

// 共享模式下获取资源的方法,响应线程中断
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return;
                }
            }
            // 和非响应线程中断的acquireShared方法类似,不过这里解除阻塞之后直接抛出异常InterruptedException
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

最后看tryAcquireSharedNanos(int arg, long nanosTimeout)的源码实现:

// 共享模式下获取资源的方法,带超时时间版本
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 注意这里只要tryAcquireShared >= 0或者doAcquireSharedNanos返回true都认为获取资源成功
        return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
}

private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 计算超时的最终期限    
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.SHARED);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return true;
                }
            }
            //重新计算剩余的超时时间 
            nanosTimeout = deadline - System.nanoTime();
            // 超时的情况下直接取消获取
            if (nanosTimeout <= 0L) {
                cancelAcquire(node);
                return false;
            }
            // 满足阻塞状态并且剩余的超时时间大于阀值1000纳秒则通过LockSupport.parkNanos()阻塞线程
            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                LockSupport.parkNanos(this, nanosTimeout);
            // 解除阻塞后判断线程的中断标记并且清空标记位,如果是处于中断状态则抛出InterruptedException 
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

共享模式的同步器的一个显著特点就是:头节点的第一个有效(非取消)的后继节点,总是尝试获取资源,一旦获取资源成功就会解除阻塞并且晋升为头节点,原来所在节点会移除出同步等待队列,原来的队列长度就会减少1,重新设置头节点的过程会传播唤醒的状态,简单来说就是唤醒一个有效的后继节点,只要一个节点可以晋升为头节点,它的后继节点就能被唤醒。节点的唤醒顺序遵循类似于FIFO的原则,通俗说就是先阻塞或者阻塞时间最长则先被唤醒。

参考:

源码分析JDK8之AbstractQueuedSynchronizer 
java并发编程——九 AbstractQueuedSynchronizer AQS详解 

AbstractQueuedSynchronizer 源码分析 (基于Java 8)  

Java并发】详解 AbstractQueuedSynchronizer 

AbstractQueuedSynchronizer使用和源码分析  

AbstractQueuedSynchronizer的介绍和原理分析  

AbstractQueuedSynchronizer框架 

深入理解AbstractQueuedSynchronizer(AQS)

JUC同步器框架AbstractQueuedSynchronizer源码图文分析  (文章主要来源)

发布了223 篇原创文章 · 获赞 308 · 访问量 84万+

猜你喜欢

转载自blog.csdn.net/maoyeqiu/article/details/95866862