JAVA并发(1)-AQS(亿点细节)
AQS(AbstractQueuedSynchronizer), 可以说的夸张点,并发包中的几乎所有类都是基于AQS的。
一起揭开AQS的面纱
1. 介绍
为依赖 FIFO阻塞队列 的阻塞锁和相关同步器(semaphores, events等)的实现提供一个框架。 为那些依赖于原子state的同步器提供基础(CyclicBarrier、CountDownLatch等). 支持独占模式和共享模式, 不同的模式需要实现不同的方法.
引用这位大佬的图 http://www.cnblogs.com/waterystone/p/4920797.html

这个图是AQS整体结构,从图中可以看到,AQS维护着一个阻塞队列(当线程获取资源失败时,就会进入该队列,等待,直到被唤醒), state是一个共享的资源。
2. 源码剖析
我们先看看AQS的类图,

内部类: Node,阻塞队列维护的元素;ConditionObject, 支持独占模式下的子类用作Condition实现, 后面会讲到。先看看Node的结构。
static final class Node {
	/** 表明阻塞一个共享模式的结点 */
        static final Node SHARED = new Node();
        /** 表明阻塞一个独占模式的结点 */
        static final Node EXCLUSIVE = null;
        /** waitStatus value to indicate thread has cancelled */
	// 发生的情况: 中断或者超时
        static final int CANCELLED = 1;
        /** 表明后继节点需要被唤醒 */
	// 发生的情况: 后继结点被park()阻塞,当目前的结点释放或取消,必须要unpark它的后继结点
        static final int SIGNAL = -1;
        /** waitStatus value to indicate thread is waiting on condition */
	// 发生的情况: 当前结点在条件队列中(后面会讲解)
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        // 发生的情况: 执行releaseShared后,需要传播释放其他结点。
        // 在doReleaseShared中进行设置。(后面讲解这个状态的必要性)
        // 在共享模式下,才会用到
        static final int PROPAGATE = -3;
	/** 结点的状态, 初始值为0*/
	volatile int waitStatus;
	volatile Node prev;
	volatile Node next;
	volatile Thread thread;
	 ....
}
tip: waitStatus > 0, 即 CANCELLED, 此时的结点不正常。
AQS使用了模板模式, 自主选择重新定义以下方法
- tryAcquire - 独占模式
 - tryRelease - 独占模式
 - tryAcquireShared - 共享模式
 - tryReleaseShared - 共享模式
 - isHeldExclusively
 
调用这些方法,都会引发UnsupportedOperationException,后面的文章将通过其子类,来讲解它们的实现。
有了这些知识后,我们从下面这几个关键的共有方法入手去讲解AQS
- acquire(int arg) - 独占模式
 - release(int arg) - 独占模式
 - acquireShared(int arg) - 共享模式
 - releaseShared(int arg) - 共享模式
 
2.1 acquire
独占模式下的获取资源,忽略中断。调用tryAcquire至少一次,若成功就返回。否则,将线程入队,并可能反复阻塞和接触阻塞,并调用tryAcquire直至成功。此方法可以用于实现 Lock.lock
    public final void acquire(int arg) {
	// (2.1.1)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
tryAcquire在后面文章结合子类进行分析。
代码(2.1.1)中,此时调用了tryAcquire,获取资源失败,返回false,继续执行后续方法。
addWaiter -- Queuing utilities
使用当前线程和给定mode, 新建一个Node,并且将新建Node入队
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
           // (2.1.2) 记住这个地方, 后面有个知识点会用到
            node.prev = pred;
           // (2.1.3)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 插入node入队,初始化如果有必要
        // 执行到这的情况:
        // 1. 阻塞队列为空时,即tail == null
        // 2. 代码(2.1.3), CAS失败
        enq(node);
        return node;
    }
enq -- Queuing utilities
一直循环直到node入队成功
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 初始时,head与tail都为空,需要新建一个哨兵结点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            // 插入结点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
经过了上面的操作,目前的线程已经加入了队尾,此时做的事情就是阻塞自己,等待资源释放并且获取,然后执行自己的操作
acquireQueued
以独占且不可中断的模式,获取已经在阻塞队列中的线程。若在等待时被中断,返回true
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 不断自旋直到获取到资源或被park
            for (;;) {
                final Node p = node.predecessor();
                // 若node的前继结点是head,执行tryAcquire,尝试获取资源(可能刚好释放了资源,就可以不用阻塞)
                // (2.1.4)
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 检测是否需要被park,若需要就进行park,并返回在等待时是否被中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //  失败,发生的情况 1. 被中断 2. 超时.(其他方法调用该方法时,会发生)
            if (failed)
                cancelAcquire(node);
        }
    }
shouldParkAfterFailedAcquire -- Utilities for various versions of acquire
对获取资源失败的node,检测并获取结点。返回true,如果线程需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 前驱结点的状态为SIGNAL,此时就可以安心的park,等待前驱节点释放资源,然后唤醒自己
        if (ws == Node.SIGNAL)
            return true;
        // 此时ws为CANCELLED
        if (ws > 0) {
            /*
             * 前驱结点状态为CANCELLED,跳过前驱结点并重试, 直到前驱节点不为CANCELLED
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus此时的值要么是0要么是PROPAGATE.
             * 需要把前驱结点状态设置为SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
cancelAcquire -- Utilities for various versions of acquire
取消正在进行的获取资源的尝试
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        node.thread = null;
        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        // 获取过滤后的前驱的后驱节点
        Node predNext = pred.next;
        node.waitStatus = Node.CANCELLED;
        // node为tail,就直接从队列中删除
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            // 1. 前驱结点不是头结点,该结点不是老二
            // 2. 满足以下任意一个条件:
                  // 2.1 此时的前驱结点状态为SIGNAL
                  // 2.2 此时的前驱结点状态为PROPAGATE或CONDITION,成功将状态设置为SIGNAL
            // 3. 前驱节点的任务不为空
            // 满足上面的条件,就将节点的前驱结点的next 指向 节点的后驱结点
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                  pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 此时结点刚好是老二;
                // 代码(2.1.4) 可以看出,头结点要么是哨兵结点,要么是已经获取到资源的结点。
                // 此时唤醒node的后驱结点,是为了防止后驱结点一直阻塞
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }
unparkSuccessor
后驱结点存在一个在正在等待的结点,则唤醒它
private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        // 将当前的结点的waitStatus设置为0,失去SIGNAL、PROPAGATE的含义
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        // (2.1.5)
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            // [问题一] 为什么node的后驱结点为空,重新寻找是从后往前找
            // 只要waitStatus <= 0, 都有机会被唤醒
            for (Node t = tail; t != null && t != node; t = t.prev)
                // (2.1.6)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
2.1.1 acquire流程图
根据上面的分析,整一个流程图

2.1.2 小结
根据acquire流程图,一句话小结其流程,尝试获取资源,失败则将新建node(当前线程及独占模式)入队,检测自己是否是老二,是老二就再一次尝试获取资源,成功就返回中断标志,不是老二就设置为SIGNAL,park自己,然后安心等待被唤醒。
2.2 release
独占模式下的释放资源。解除阻塞一个或多个线程,当tryRelease返回true时。此方法可以用于实现 Lock.unlock
    public final boolean release(int arg) {
        // tryRelease返回true,继续下面操作。
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
2.2.1 release流程图
因为release(int arg)的主要流程是在tryRelease和unparkSuccessor中,但是tryRelease又是在子类中实现,所以该流程图也可以看作unparkSuccessor的流程图

2.2.2 小结
根据release流程图, 一句话小结其流程, 释放资源,唤醒后驱没有被取消的结点。
下面讲讲AQS的另一种模式,共享模式
2.3 acquireShared
共享模式下获取资源,忽略中断。至少调用tryAcquireShared一次,成功就返回。否则,线程将入队,可能会重复的阻塞和解除阻塞,直到调用tryAcquireShared成功。成功获取到资源,将会唤醒后驱结点,若资源满足。
    public final void acquireShared(int arg) {
        // 在子类探究tryAcquireShared
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
对tryAcquireShared返回的参数,进行简单的介绍
- 返回负数表示失败;
 - 返回零,随后的线程都不能获取到资源
 - 返回正数,随后的线程可以获取到资源
 
此时tryAcquireShared的返回值是小于零,表示获取资源失败,进行下一步处理
doAcquireShared
获取资源在不可中断的模式下
private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    // 若前驱结点是头结点且刚好释放了,尝试获取资源
                    // (2.3.1)
                    if (r >= 0) {
                        // 将当前node设置为head,并且尝试唤醒node的后驱结点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 跟独占模式的处理是一样的
                // (2.3.2)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
setHeadAndPropagate
设置队列的头结点,达到条件就唤醒后面的结点.
private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        // tips: h == null、(h = head) == null、s == null,是为了防止空指针的发生
        // 执行到下面的情况:
        // 1. propagate是tryAcquireShared返回的值。propagate(资源) > 0, 表示还有资源可以唤醒后面的结点。
        // 否则,此时propagate = 0, 结合代码(2.3.1)
        // 2. 旧的head的waitStatus < 0
        // 旧的头结点释放了资源,执行了代码(2.3.4), 此时的waitStatus 为PROPAGATE(初始化为0)
        // 3. 此时的head已经是当前结点了,后面若有结点(此时后面的结点在park),
        // 那么新head的waitStatus肯定为SIGNAL, 结合代码(2.3.2)
        // 情况3,有可能会发生没必要的唤醒,因为此时去唤醒新head的后驱结点,但是此时还没
        // 有释放资源,它后驱结点唤醒后,去获取资源,获取失败,又被park.
        // 源码注释说到,虽然有争议,但是大多数情况下,需要去唤醒
        // (2.3.3)
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
代码(2.3.3)中, 为什么不只用propagate来判断是否唤醒后驱结点 [问题二]
doReleaseShared
共享模式下主要的释放资源的逻辑,唤醒后驱结点,确保线程不被挂起
 private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    // (2.3.4)
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                // (2.3.5)
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
2.3.1 acquireShared的流程图

2.3.2 小结
一句话小结acquireShared的流程,尝试获取资源,若获取到资源,资源还有剩余就去继续唤醒后驱结点,若尝试获取资源失败,就park自己,等待被唤醒。 跟acquire相比,最大的区别就是,获取到资源acquireShared,还会去尝试唤醒其后驱结点
2.4 releaseShared
Releases in shared mode. Implemented by unblocking one or more threads if tryReleaseShared returns true.
    // 代码比较简单,就不分析了~
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
2.5 问题解答
[问题一] 为什么在唤醒后驱结点时,node的后驱结点为空,需要重新从后往前找
   // (2.1.2) 记住这个地方, 后面有个知识点会用到
   node.prev = pred; // 1
   // (2.1.3)
   if (compareAndSetTail(pred, node)) { // 2
      pred.next = node; // 3
      return node;
   }
仔细观察代码(2.1.2) 和(2.1.3), 此时添加结点相当于有三步,都不是原子性的,当执行到第二步时,就要唤醒后驱结点了,此时新增的结点只设置了前驱结点,队列设置了尾结点,但是没有设置后驱结点,如果从前往后查找的话,可能会丢失符合要求的结点。
[问题二],代码(2.3.3)中, 为什么不只用propagate来判断是否唤醒后驱结点。
请看这位大佬的博客 讲的非常详细
大致意思就是,
我们假设有A、B、C、D四个线程,前两个释放资源的线程,后两个是争抢资源的线程,此时只有A或B释放了资源,C、D才可以被唤醒,假设我们不看PROPAGTE
时刻一: A线程释放资源,执行代码 (2.3.4),head的waitStatus从SIGNAL(-1)变为了0
时刻二: C线程获取到资源,执行到代码(2.3.1), tryAcquireShared返回0
时刻三: B线程线程释放资源,执行代码 (2.3.4),因为此时未改变头结点,head的waitStatus为0,不能unparkSuccessor
时刻四: 此时C执行到代码(2.3.3),propagate(tryAcquireShared返回值)为0,C也不会去唤醒后驱结点,D线程就永远GG了
引用doReleaseShared注释中的一句话
status is set to PROPAGATE to ensure that upon release, propagation continues.
2.6 Condition
使用synchronized时,线程间通信使用wait, notify and notifyAll;而使用AQS实现的lock,线程间的通信就使用Condition中的await、signal...。Condition与Lock结合使用,同一个lock对应多个Condition。

public class ConditionObject implements Condition...
在AQS中,已经对Condition的方法进行了实现,子类想使用的话,只需要调用ConditionObject就行了
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
本来想跟着源码走,简简单单介绍一下Condition,但是源码有几处细节,让我头秃,在网上搜索别人的博客,这篇博客解开了我的疑惑,对Condition介绍的非常详细,写的非常的完美~
根据大佬的博客,那我们下面简单讲解Condition的两个常用方法
- await
 - signal
 
2.6.1 await & signal
导致目前线程阻塞直到被唤醒或中断;调用await后,会将当前的线程封装成node,加入到条件队列中
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 添加结点到条件队列中
            Node node = addConditionWaiter();
            // 释放当前线程持有的锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 不在同步队列中,就park
            // (2.6.1)
            while (!isOnSyncQueue(node)) {
                // 执行到这,当前线程会立即挂起
                LockSupport.park(this);
                // 运行到这的话,情况: 1. signal 2. 中断
                // 检验中断
                // (2.6.2)
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 第二部分
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
为了讲清楚代码(2.6.1)之后的逻辑,我们先看看signal的源码
将condition queue中等待最长的结点转移到sync queue中去,去争抢资源
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
此时,执行signal的主要逻辑
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 将后驱结点置空
                // (2.6.3)
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
将condition queue中的一个结点转移到sync queue中去
    final boolean transferForSignal(Node node) {
        // 这里表示当前已经被取消了。
        // (2.6.4)
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 将当前结点放入sync queue的末尾, 此时返回的是当前结点的前驱结点(一定要注意)
        Node p = enq(node);
        int ws = p.waitStatus;
        // 前驱结点被取消,或者设置SIGNAL状态失败,就直接唤醒当前线程, 唤醒 = 有资格去竞争锁了
        // enq返回的是前驱结点,我人傻了,看成是返回当前线程,就一直觉得逻辑不对。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
这里signal的逻辑就讲完了,总结一下:
- 在condition queue中找出等待时间最长且未被取消的结点, 转移到sync queue的队尾去,同时要在condition queue中删除该结点
 - 若在sync queue中的该结点的前驱结点被取消了或设置SIGNAL状态失败,要直接唤醒它,叫它去竞争锁。
 
signalAll的主要逻辑和signal是一样的,差别就是signalAll会把所有在condition queue中的结点转移到sync queue中去,并清空所有在condition queue中的结点,下面只贴一下signalAll的主要代码,
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }
我们再次回到await中去
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 添加结点到条件队列中
            Node node = addConditionWaiter();
            // 释放当前线程持有的锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 不在同步队列中,就park
            // (2.6.1)
            while (!isOnSyncQueue(node)) {
                // 执行到这,当前线程会立即挂起
                LockSupport.park(this);
                // 运行到这的话,情况: 1. signal 2. 中断
                // 检验中断
                // (2.6.2)
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 第二部分
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
从代码(2.6.1),继续讲解
isOnSyncQueue
一开始在条件队列中,现在在sync queue中等待重新获取资源,如果有这种的node就返回true
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }
findNodeFromTail
从尾部找寻结点
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            // 结合下面的enq代码以及图思考一下,会明白此方法的意义的
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }
node.waitStatus == Node.CONDITION, 表示当前结点肯定在condition queue中。
为何是上面的那些条件?
我们上面看了转移到sync queue是用的enq方法
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // (2.6.5)
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
结合代码(2.6.5),思考一下就知道isOnSyncQueue中条件设置的道理了,但是为何需要findNodeFromTail啦? 这是需要补充一个知识点了,在多个线程执行enq时,只有一个线程会设置为tail成功,其余的都只是设置prev,就可能会出现下面图片中的情况,'多个尾巴'。一直不断自旋,最后会变成一个正常的链表。

此时线程的状态是,调用await后,将结点添加到条件队列中,且释放了自己持有的所有资源,并将自己park,此时等待被signal或者被中断。
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 添加结点到条件队列中
            Node node = addConditionWaiter();
            // 释放当前线程持有的锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 不在同步队列中,就park
            // (2.6.1)
            while (!isOnSyncQueue(node)) {
                // 执行到这,当前线程会立即挂起
                LockSupport.park(this);             ///  我们在这被挂起了
                // 运行到这的话,情况: 1. signal 2. 中断
                // 检验中断
                // (2.6.2)
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 第二部分
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
执行到代码(2.6.2), 我们直到可能是被signal或被中断了。现在要解决的是,
- 是否被中断?
 - 何时被中断?
 - 中断如何处理?
 
我们带着这三个问题,继续出发~
补充一个小知识点,AQS定义了三种情况中断的值
- THROW_IE, signal前被中断,要抛出InterruptedException
 - REINTERRUPT, signal后被中断
 - 0, 未被中断
 
关于REINTERRUPT这个中断,可以理解成,吃饭,吃完了但是还有一个菜没有上,问服务员,“如果没有炒,就不要了”,但是服务员告诉,菜已经下锅,所以这时候的中断就是REINTERRUPT,中断的太晚了。 -- 例子来自上面的那篇博客
我们继续看向代码(2.6.2)
checkInterruptWhileWaiting
        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
若被中断Thread.interrupted肯定为true
transferAfterCancelledWait
    final boolean transferAfterCancelledWait(Node node) {
        // 中断情况一: 此时结点还在condition queue中,肯定是signal前就被中断了
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            // 这里还要加入到sync queue中,获取到锁才能抛出错,继续往后看
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
         // 中断情况二: 这里的情况就是signal后,node还没有执行enq,毕竟执行signal到执行enq还有几个步骤
         // 此时就自旋,等待node转移到sync node中就行了
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }
上面的代码已经注明了,各种情况的发生时机,此时我们来到了await的第二部分~
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 添加结点到条件队列中
            Node node = addConditionWaiter();
            // 释放当前线程持有的锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 不在同步队列中,就park
            // (2.6.1)
            while (!isOnSyncQueue(node)) {
                // 执行到这,当前线程会立即挂起
                LockSupport.park(this);             ///  我们在这被挂起了
                // 运行到这的话,情况: 1. signal 2. 中断
                // 检验中断
                // (2.6.2)
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 第二部分
            // acquireQueued获取到锁,并返回是否被中断。
            // 有一种情况需要提一下,acquireQueued返回true,上面的interruptMode = 0,
            // 表示signal后,在获取锁的时候被中断了
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // node还在condition上,说明是被取消了的node,清除它
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                // 对上面得到的interruptMode做出回应
                reportInterruptAfterWait(interruptMode);
        }
reportInterruptAfterWait
        private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
2.6.2 await与signal的流程图


2.6.3 小结
先讲讲await的流程,看起流程图有点吓人,其实很多步骤是对不同时机的中断操作的记录
当await被执行,下面简单总结下await的流程
- 将当前线程与CONDITION状态封装成node,加入到condition queue的末尾
 - 释放线程之前获取的所有资源
 - 若不在sync queue中,阻塞自己,等待被signal或被中断
 - 获取中断操作的时机,并记录表示何时中断的值(interruptMode)
 - 不管是怎么被唤醒的,都要去竞争资源,直到获得资源为止
 - 最后对不同的中断值,作出不同的操作
 
signal的流程就相对于简单一点
- 获取condition queue的头结点
 - 检验是否被取消,若被取消,就获取头结点的后驱结点,以此类推;
 - 将结点从condition queue中转移到sync queue中,而且会从condition queue中删除该节点
 - 若结点插入sync queue,得到的前驱结点,被取消了,或者CAS前驱结点状态为SIGNAL
失败,将直接unpark当前线程 
3. 总结
Doug Lea,太秀了。AQS中有很多细枝末节的东西,只有自己去读了源码,理解为何这样做,你才会明白才会真正读懂AQS。
关于学习和写AQS文章时,看了一些博客,为我解答了自己的疑惑,慢慢加油,我也要向这些大佬看齐~
4. 参考
- 《Java并发编程之美》 - 这本书可以作为学习并发的入门书
 - Java并发之AQS详解 - 引用了他的图片
 - AbstractQueuedSynchronizer源码解读 - 为我解开了一些获取和释放资源的疑惑
 - 逐行分析AQS源码(4)——Condition接口实现 - 为我解开了一些Condition的疑惑
 - AQS论文 Doug Lea
 
~~# 5. 面试中问题
这是我的一个想法,若我博客中写过的知识,在面试中有问到过,我会记录下来,没有就是目前还没遇到过
JAVA并发(1)-AQS(亿点细节)的更多相关文章
- JAVA并发-同步器AQS
		
什么是AQS aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLo ...
 - 深入理解Java并发框架AQS系列(一):线程
		
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
 - 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
		
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
 - 深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
		
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...
 - 【转】Java并发的AQS原理详解
		
申明:此篇文章转载自:https://juejin.im/post/5c11d6376fb9a049e82b6253写的真的很棒,感谢老钱的分享. 打通 Java 任督二脉 —— 并发数据结构的基石 ...
 - Java并发编程--AQS
		
概述 抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态, ...
 - 深入理解Java并发类——AQS
		
目录 什么是AQS 为什么需要AQS AQS的核心思想 AQS的内部数据和方法 如何利用AQS实现同步结构 ReentrantLock对AQS的利用 尝试获取锁 获取锁失败,排队竞争 参考 什么是AQ ...
 - 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock)
		
一.前言 优秀的源码就在那里 经过了前面两章的铺垫,终于要切入正题了,本章也是整个AQS的核心之一 从本章开始,我们要精读AQS源码,在欣赏它的同时也要学会质疑它.当然本文不会带着大家逐行过源码(会有 ...
 - Java并发框架——AQS中断的支持
		
线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...
 
随机推荐
- 前端-CS-04
			
一:DOM(文档对象模型) document 简写DOM 1.DOM中定义变量用 var 如下截图中:定义demo变量 2.取一个input输入框中的值的方法: 1)先如1中,在dom中顶一个一个变 ...
 - 普通 Javaweb项目模板的搭建
			
普通 Javaweb项目模板的搭建 1. 创建一个web项目模板的maven项目 2.配置 Tomcat 服务器 3.先测试一下该空项目 4.注入 maven 依赖 5.创建项目的包结构 6.编写基础 ...
 - Rancher 安装和使用-实践
			
Rancher 安装和使用 Rancher是一个完整的,开源的平台,用于在生产环境中部署和管理容器.它包括Kubernetes,Mesos和Docker Swarm的商业支持发行版,使得在任何基础架构 ...
 - 通过lms.samples熟悉lms微服务框架的使用
			
经过一段时间的开发与测试,终于发布了Lms框架的第一个正式版本(1.0.0版本),并给出了lms框架的样例项目lms.samples.本文通过对lms.samples的介绍,简述如何通过lms框架快速 ...
 - 【Python学习笔记】-虚拟环境virtualenv
			
在开发python应用程序的时候,系统安装的python3只有一个版本:3.4.所有的第三方的包都回被pip安装到python3的site-packages目录下. 如果我们要要同时开发多个应用程序, ...
 - (十三)Docker容器进入的4种方式
			
简介 在使用Docker创建了容器之后,大家比较关心的就是如何进入该容器了,其实进入Docker容器有好几多种方式,这里我们就讲一下常用的几种进入Docker容器的方法. 进入Docker容器比较常见 ...
 - show engine innodb status 输出结果解读
			
show engine innodb status 输出结果解读 基于MySQL 5.7.32 最近想整理一下show engine innodb status的解读,但是发现中文互联网上相关的信息要 ...
 - Mybatis(一)Porxy动态代理和sql解析替换
			
JDK常用核心原理 概述 在 Mybatis 中,常用的作用就是讲数据库中的表的字段映射为对象的属性,在进入Mybatis之前,原生的 JDBC 有几个步骤:导入 JDBC 驱动包,通过 Driver ...
 - Java双刃剑之Unsafe类详解
			
前一段时间在研究juc源码的时候,发现在很多工具类中都调用了一个Unsafe类中的方法,出于好奇就想要研究一下这个类到底有什么作用,于是先查阅了一些资料,一查不要紧,很多资料中对Unsafe的态度都是 ...
 - fastjson反序列化漏洞实际案例利用
			
fastjson反序列化rce实际案例利用全过程: 存在问题网站:http://***.com/ 在网站上寻找一些安全漏洞的时候,发现一条json数据包 数据包如下: POST /*** HTTP/1 ...