AbstractQueuedSynchronizer源码分析

AbstractQueuedSynchronizer(AQS)是抽象队列同步器,为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁定和相关同步器(信号量、事件,等等)提供了一个基础框架。

AQS最基本的数据结构是Node,等待队列中维护的对象就是Node结点。Node是AQS中定义的静态final的内部类,其中定义了waitStatus, prev, next, thread和nextWaiter等几个属性,并且定义了两种模式:独占模式和共享模式。等待队列是CLH(Craig, Landin, Hagersten)锁队列的另一种形式,thread的部分控制信息,这里主要是指状态信息, 是由thread所在Node的前继(Predecessor)结点维持的。每一个Node都有一个status域来跟踪并决定其thread是否阻塞。当Node结点的前继被释放的时候,这个Node会收到Signal信号。

 

Node的内部结构:

volatile int waitStatus:

这是Node的状态域,它的取值只可能是以下几种:

1. Node.CANCELLED (1): 表示Node所持有的线程已被取消,原因可能是timeout或者是被中断、或者被设为null了。Node结点只能是暂时停留在此状态,因为在线程进入AQS时,线程会找机会整理链表,包括删除CANCELLED状态的结点。例如,ConditionObject.awati()会调用unlinkCancelledWaiters()删除被Cancelled的结点。此外,被取消的结点上的线程不会再阻塞。

2. Node.SIGNAL(-1):  表示Node的后继结点(Succesor)的线程需要unparking,简单说就是需要唤醒后继结点。由于Node的后继结点被park方法阻塞了(入队列的时候就被阻塞了),所以, 当这个Node被释放或取消的时候,必须unpark它的后继结点(unpark相当于unblock)。为了避免竞争,acquire方法必须首先指明它需要一个signal信号,然后尝试原子性的acquire,如果失败,则阻塞线程。

3. Node.CONDITION(-2): 表示Node在条件队列中,线程在条件等待。在它被转移到同步队列(sync queue)之前,它不会被用个同步队列的结点。被转移到同步队列的时候,Node的状态会被设为0。

4. Node.PROPAGATE(-3): 表示下一次调用acquireShared应该无条件传播。releaseShared操作应该要传播给其他的结点。这个值是在doReleaseShared为头结点设置的,从而保证传播能够继续下去。这个状态是在共享模式下使用的,加入此状态,可以避免不必要的线程 park 和 unpark操作。

5. 0值,表示Node不是处于以上的任何一种状态。

 

volatile Node prev:

prev指向当前结点的前继结点,当前结点或线程依赖于其前继结点来检查它所处的状态。prev在入队列的时候指定,出队列的时候将它设为null。如果前继结点被取消,则需要找到一个没有被取消的结点。头结点永远不可能被取消,因为只有获取操作(acquire)成功了,这个结点才有可能成为头结点;被取消的线程是不可能acquire成功的,而且线程只会取消它自己而不是其他结点。因此,我们总会找到一个没被取消的结点,这个结点有可能是头结点。

 

volatile Node next:

next指向当前结点的后继结点,当前结点或线程被释放的时候会unpark其后继结点。next域在入队列的时候指定,出队列的时候设为null。如果Node的前继结点被取消,就要修改Node的第一个没有被取消的前继结点的next域,将它指向当前结点。enq()并没有指定新结点的前继结点的next域,直到有后续的新结点加入进来的时候才会指定next域。所以next=null并不意味着这个结点是队列的最后一个结点。然而,如果next=null,我们可以从队尾double-check其前继结点来决定是不是队列的最后一个结点。被取消的结点的next域指向它自己(node.next=node),而不是设为null,这可以简化isOnSyncQueue()实现。

 

volatile Thread thread:

Node所对应的线程。

 

Node nextWaiter:

指向在条件队列中等待条件的下一个结点或者是SHARED结点(在共享模式中等待的结点)。因为条件队列仅允许在独占模式下访问,所以我们只需要一个简单的链式队列管理在等待条件的每一个结点。条件队列的结点会被转移到同步队列,然后重新调用acquire。SHARED结点表明是在共享模式下。

 

AQS的数据域:

private transient volatile Node head;

 同步队列的头结点,延迟初始化。队了初始化之外,只能通过setHead()来修改头结点。如果存在头结点,则它的waitStatus不可能是CANCELLED。其实,头结点只是一个标记,表明它是队列的头,当初始化的时候,它指向一个dummy的结点。如果队列不是空的,那头结点一般就是上一次出队列的那个结点;只不过,出队列后,它便不再持有任何线程,它也没有前继结点了,也就是说变成了队列头的标记。

 

private transient volatile Node tail;

 同步队列的尾结点,延迟初始化。只能通过enq()方法在队尾添加新的结点。

 

private volatile int state;

state是当前同步器类的状态,这是一个整数的状态信息,可以通过getState(), setState()和compareAndSetState等protected类型方法来进行操作。这个state可以表示任何状态,因为AQS的子类有各种不同的含义。例如,ReentrantLock用它来表示所有者线程重复获取该锁的次数,Semaphore用来表示剩余的许可数量,FutureTask用它来表示任务的状态(尚未开始、正在进行、已经完成以及取消)。在AQS中还可以自行管理一些额外的状态变量,例如,ReentrantLock保存了锁的当前所有者信息,以区分某个获取操作是重入的还是竞争的。

 

AQS维护着两种不同的队列:1. Sync 同步队列; 2. 条件队列。但是同步队列和条件队列都使用同样的Node结点。同步队列是双向队列,使用node.prev, node.next指向结点前继和后继结点,有头结点head和尾结点tail,可以通过head和tail访问同步队列。head和tail在初始化的时候都指向一个dummy的结点。队列中每个结点的状态信息都保存在其前继结点中。条件队列是一个单向链表结点的队列,node.nextWaiter专用于条件队列,指向下一个在条件队列中等待条件的结点。

 

同步队列入队列操作enq()和addWaiter()

 

private Node enq(final Node node) {
        for (;;) {
            Node t = 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;
                }
            }
        }
}

 enq()将node结点插入同步队列的尾部。如果当前队列tail是null,初始化队列,设置队列的头结点,将tail指向head。否则,tail不为null,将当前结点node的前继结点引用指向当前队列尾结点,调用原子操作compareAndSetTail()将node设为新的队列尾结点。如果compareAndSetTails()操作失败,说明队列的tail已经被改变,则回到for循环开始的地方重新获取tail,直到成功把node设置为tail,最后修改旧的队列尾结点的next域,将它指向当前的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) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
}

 此外,还有一个 addWaiter()方法,它以指定的独占或共享模式创建一个新的结点,并将它插入队列。注意到,它的thread域是当前线程。它先尝试快速入队列的方法,也就是直接把它加到队列尾部。如果成功,则返回新插入的node。否则,调用 enq()方法将新结点入队列。

 

同步队列出队列操作

setHead()

private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
}
这个操作将当前结点Node设置为头结点,将node所持有的线程置null,并切断与前继结点的关联,从而将结点移出同步队列。对thread和pred的操作有利于GC垃圾回收,而且可以避免收到不必要的信号和遍历。

 

同步器的基本操作

 

同步器需要支持的最基本的操作只有两个:获取操作acquire和释放操作release。这两个操作大体上看是这样实现的:

 Acquire:
      while (!tryAcquire(arg)) {//尝试获取失败,也就是说,同步状态不允许获取操作
         如果线程尚未进入同步队列,将线程插入同步队列;
         阻塞当前线程;
      }
 
 Release:
      if (tryRelease(arg)) //释放操作成功
         唤醒在同步队列中等待时间最长的线程,即队列中的第一个结点所持有的线程;
 

AQS的基本操作不止这两个。在独占模式下支持阻塞的获取操作acquire(),可中断的获取操作acquireInterruptibly(),可中断、可超时退出的获取操作tryAcquireNanos() 以及释放操作release()。共享模式下支持可阻塞的acquireShared(),可中断的获取操作acquireSharedInterruptibly(),可中断、可超时退出的获取操作tryAcquireSharedNanos()以及释放操作 releaseShared()。

 

acquire操作

这是独占模式下的获取操作,它不会被中断。tryAcquire()是由AQS的子类定义的,至少会被调用一次。如果tryAcquire()成功了,则可以直接返回。如果失败,则将当前线程插入到同步队列中,那么这个线程就被阻塞了。在阻塞的过程中,不断调用tryAcquire(),直到到获取操作成功才会返回。

 

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
 

 

 

acquireQueued()

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

 在线程阻塞等待获得锁的过程中或者是执行condition的await()时,都会调用acquireQueued()方法,其中的主体是for循环。这个for循环做什么了呢?

首先,获取当前结点node的前继结点,判断它是不是head,如果不是head,获取操作就失败了。p==head的判断保证了只有head的后继结点才可能被设置成新的头结点。所以,并不是说队列中的任何一个结点都可以被设置为头结点的。

如果node的前继结点是head,调用tryAcquire()尝试获取锁,当然这个tryAcquire的具体实现是由AQS的子类定义的。如果获取操作成功,则调用setHead()将当前结点node设置成头结点,注意setHead()会把node.thread和node.prev置空,然后解除node 的前继结点与当前结点node的关系(p.next=null)。最后返回结果。

如果获取操作失败了,或者node不是head的下一个结点,调用shouldParkAfterFailedAcquire()检测获取操作失败后是否需要通过park()将线程挂起。下面分析一下这个方法。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
}

 shouldParkAfterFailedAcquire( )的调用有一个前提:pred=node.prev,也就是第一个参数结点是第二个参数结点的前继结点。它先获取前继结点的waitStatus。如果waitStatus=Node.SIGNAL,它的状态要求一个release操作发送信号唤醒它的线程(node.thread),也就是说这个线程其实已经是阻塞的了。所以,调用park()方法是安全的。

除此之外,其他的状态下都不能立刻决定是否需要调用park()阻塞线程,下一轮循环再判断是否需要这样做。第一种情况,waitStatus > 0,即Node.CANCELED,说明node的前继结点已被取消了,那就需要从pred的前继结点开始向前遍历查找一个waitStatus小于0的结点,并将它设置成node的前继结点,然后将它的后继结点设为node。

第二种情况,waitStatus < 0,则waitStatus只可能是0或Node.PROPAGATE,因为1)它不可能是Node.SIGNAL;2) 这是同步队列,它不可能是Node.CONDITION。在这种情况下,我们需要获得一个SIGNAL信号,但是还不能调用park()挂起线程, 所以这里调用compareAndSetWaitStatus()原子操作将前继结点的waitStatus设成Node.SIGNAL。如果下一轮循环获取操作依然不成功,则可以安全地能过park()将线程挂起了。

如果shouldParkAfterFailedAcquire()返回true,则调用parkAndCheckInterrupt()。它做的事情很简单,调用park()将当前线程挂起,然后检查线程是否被中断,并返回查检的结果。

 

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

 回到acquireQueued( )方法,它有一个finally块,如果获取操作失败,则调用cancelAcquire( )取消正在进行的尝试获取锁的操作。doAcquireInterruptibly(), doAcquireNanos()等方法都有可能抛出InterruptedException从而导致获取操作失败,从而触发cancelAcquire( )的调用。但是在这里,for循环中似乎所有逻辑和方法调用都不会抛出Exception,当前线程会一直阻塞,直到它成功地获取了锁。那它怎么才有可能出现获取失败的情况呢?有可能抛出Error的是tryAcquire( )方法。例如,在 ReentraceLock中,tryAcquire( )方法可能抛出Error("Maximum lock count exceeded")。那么这就有可能失败,于是就会调用cancelAcquire( )方法。

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;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            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 {
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
}

 在acncelAcquire中,node不能是null。具体过程:

1) 它将node.thread置null。

2) 从node的前继结点向前遍历,忽略所有被取消的结点,找出第一个没有被取消的结点,将它设为node的前置结点。

3) 获取前继结点的下一结点,这个结点是需要从同步队列中拿掉的。

4) 将node的同步状态设为Node.CANCELLED。这一步之前,其他线程不能干扰这个线程的操作。这一步之后,其他结点/线程可以忽略这个结点。

5) 如果当前结点是tail结点,将前继结点设为tail,并将前继结点的next域置null。

6) 如果当前结点不是tail结点,判断后继结点是否需要signal信号。如果需要,而且next结点没有被取消,则将当前结点node的前继结点的后继结点设置为当前结点node的后继结点。相当于node.prev.next=node.next。如果不需要signal信号,则调用unparkSuccessor()去唤醒后继结点线程,因为需要唤醒的线程是由后继结点持有的。最后将当前结点的next域指向自己,表明这个结点已被取消。

 

下面再分析一下unparkSuccessor( )方法。

 

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
}

 如果当前结点的waitStatus < 0,把它设置成0,就算compareAndSetWaitStatus()失败了或者设成0之后又被其他等待线程修改过也没关系。需要unpark的线程是由后继结点持有的。这个后继结点一般是next所指向的结点。但是,next所指向的结点可能是null,也可能已经被取消了。这种情况下,需要从同步队列的tail结点向前遍历直到找到一个没被取消的后继结点或者遇上了当前结点node。如果找到这样的一个后继结点,就将它持有的线程唤醒,即调用LockSupport.unpark()。

 

以上的分析比较完整地介绍了独占模式下acquire()操作的实现。acquireInterruptibly()和 tryAcquireNanos()首先检测一下当前线程是不是已被中断,如果是,则抛出InterruptedException;否则,调用tryAcquire(),失败时再分别调用doAcquireInterruptibly()和doAcquireNanos()。这两个方法与acquireQueued()的实现基本是一样的。不同点在于,

1) 线程被阻塞后,如果检测到线程被中断了,两个方法都抛出InterruptedException;

2) doAcquireNanos(),如果在给定的时间内获取操作不成功,那它就会因超时失败而退出;

3) 线程中断后或超时退出后,线程不再处于被阻塞的状态。

 

release()操作

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}

 这是独占模式下的释放操作。如果tryRelease()返回true,在头结点的状态不为0的情况下,如果存在后继续点,则唤醒它的后继结点所持有的线程,即在同步队列中等待时间最长的线程。

 

acquireShared操作

 

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
}
 这是共享模式下可被阻塞的获取操作。AQS子类定义的tryAcquireShared()至少会调用一次。如果tryAcquireShared()返回值大于或等于0,则获取操作是成功的。否则,调用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);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}
 doAcquireShared(),将当前线程插入同步队列中,不断地调用tryAcquireShared()直到获取操作成功。在这个过程中,线程会被阻塞。它的实现跟acquireQueued()很相似。不同点是:1) 它调用tryAcquireShared()在共享模式下尝试获取操作,其实返回值大于0才表示获取操作成功;2) 除了将当前结点移出队列外,还做了一些额外的工作: 如果tryAcquireShared()返回值大于0, 或者head为null,或者头结点的waitStatus小于0(PROPAGATE状态可能会变成SIGNAL),而且后继结点是在共享模式下或者null,则唤醒后继结点并保证把释放操作传播给其他结点,这些都是在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) 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.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
}

 下面看看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) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
}
 doReleaseShared()中的head其实setHeadAndPropagate()中出队列的那个结点。其主要过程:

1. 如果head结点的waitStatus是Node.SIGNAL,则说明需要唤醒它的后继结点持有的线程。在唤醒它的后继结点之前,我们需要确定CAS 重置状态信息的操作是否成功。如果失败,需要重新开始检测头结点的waitStatus;否则,唤醒后继结点持有的线程。

2. 如果head结点的waitStatus是0,则不需要唤醒后继线程,但是要把头结点的状态设置成Node.PROPAGATE。

3. 如果在做1和2的时候,如果头结点被改变了,就要重新循环一次。

 acquireSharedInterruptibly()和tryAcquireSharedNanos (),首先检测一下当前线程是不是已被中断,如果是,则抛出InterruptedException;否则,调用 tryAcquireShared(),失败时再分别调用doAcquireSharedInterruptibly()和doAcquireSharedNanos()。这两个方法与 doAcquireShared()的实现基本是一样的。不同点的地方:

1) 线程被阻塞后,如果检测到线程被中断了,两个方法都抛出InterruptedException;

2) doAcquireSharedNanos(),如果在给定的时间内获取操作不成功,那它就会因超时失败而退出;

3) 线程中断后或超时退出后,线程不再处于被阻塞的状态。

 

 releaseShared()操作

 

 共享模式下的释放操作releaseShared,比较简单。如果tryReleaseShared()返回true,也就是修改状态信息以表示在共享模式下的释放操作成功,那么将调用doReleaseShared()。

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
}

 

条件队列

 

先了解一下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();
}

 AQS的ConditionObject实现了Condition接口,从而提供了上述Condition接口所支持的操作。Java有内置的条件队列,其实现依赖于Object对象的wait(), notify(), notifyAll()等方法。内置条件队列存在一些缺陷。每个内置锁都只能有一个相关联的内置条件队列,但是多个线程可能在同一个条件上等待不同的条件谓词,并且在最觉的加锁模式下公开条件队列对象。这些因素都使得无法满足在使用notifyAll时所有等待线程为同一类型的喜怒需求。如果想写一个带有多个条件谓词的并发对象,或者想获得除了条件队列之外的更多控制权,就可以使用显式的Lock和Condition而不是内置锁和内置条件队列。正如内置条件队列是与内置锁关联在一起的(wait, notify, notifyAll只能在synchronized块内使用),Condition也与Lock关联在一起,只能在获得锁的情况下才使用Condition的操作。Codition条件队列更灵活,每个Lock都可以有任意数量的Condition对象,每个锁上可以存在 多个等待,条件等待可以阻塞的,也可以被中断,或者超时退出,而且支持公平的或者非公平的队列操作。对于公平的锁,线程会依照FIFO顺序从Condition.await中释放。

 

AQS中的条件队列是由Node结点组成的单向链式队列。AQS的内部ConditionObject主要的两个属性是:1) firstWaiter: 条件队列的第一个结点;2) lastWaiter:条件队列的最后一个结点。条件队列上的每个结点依赖于Node.nextWaiter串联起来(Node.next是同步队列使用的)。

                      +--------+  nextWaiter   +--------+               +--------+
      firstWaiter |           |   -------------> |            | ---------->|            |  lastWaiter
                      +--------+                     +---------+              +---------+

 

这里着重分析一下await()和signal()方法。

await()方法:

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                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);
}

 

await()实现了可中断的条件等待。具体过程如下:

  1. 如果当前线程被中断,抛出InterruptedException。
  2. 将当前线程包装成Node加入条件队列。
  3. 以同步器当前的状态值为参数调用独占模式下的释放操作release()。release()成功则返回当前的状态信息;否则,抛出IllegalMonitorStateException,并将当前线程的结点标记为已取消。
  4. 在while循环中阻塞线程,直到线程被中断或线程收到Signal信号。在这个循环中,不断检测通过checkInterruptWhileWaiting()检测线程是否被中断,如果返回值是0,则线程没有被中断;通过isOnSyncQueue()检测是否收到Signal信号,如果已被Signalled,则线程所在结点的next域肯定不是null了,因为这个结点已经从条件队列转移到了同步队列。
  5. 以3返回的状态值为参数调用acquireQueued()重新获取锁,然后设定中断模式。 REINTERRUPT: 退出条件等待时重新中断线程;THROW_IE: 退出条件等待时抛出InterruptedException。
  6. 清除被取消的结点/线程。
  7. 如果线程在第4步被中断了,则调用reportInterruptAfterWait()根据interruptedMode抛出 InterruptedException或重新中断线程。

await()所涉及的方法调用:

 

addConditionWaiter():

 

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
}
 这个方法主要是将当前线程包装成Node加入条件队列中去,实现很简单。如果条件队列为空,则将Node设为第一个结点;如果不为空,则将它加到条件队列 的末尾。注意,在当前线程入队列前,它先检测队列的最后一个结点是否被取消了,如果是,则通过unlinkCancelledWaiters()将它清理 掉。

 

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;
            }
}
 unlinkCancelledWaiters()遍历整个条件队列,将所有被取消的结点清除出队列,最终使得队列中所有结点者处于Node.CONDITION状态。被清除出队列的结点随时可以被垃圾回收。这个方法只能在持有锁的情况下才能调用。从源代码来看,它只会被ConditionObject的await()、awaitNanos()、awaitUtil()、addConditionWaiter()调用。其实,只有两种情况需要清理条件队列:1) 在条件等待的过程中,有线程被取消了; 2) 在加入新结点到条件队列的时候发现队列的最后一个结点处于被取消的状态。

 

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
}
fullyRelease()方法已经在await()的第3步分析过了,不再赘述。

 

isOnSyncQueue()

 

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);
}
 isOnSyncQueue()主要作用就是检查结点node是否在同步队列上。如果结点的状态是Node.CONDITION或者不存在前继结点,那么它肯定还在条件队列上。如果这个结点存在一个由next域指示的后继结点,则该结点必定已转移到同步队列上了,因为在条件队列我们是用nextWaiter指示其后继结点的,而且在条件队列上的结点next域全是null。
若到此依然不能判断该结点是否在同步队列上,我们需要在同步队列上查找该结点了。可能存在这种情况:结点的前继结点或许非空的,但是并不在同步队列上。这是因为把该结点放到同步队列上的CAS操作可能失败了。所以,需要从同步队列的队尾tail开始遍历,确定该结点确实被加到同步队列上去了。如果CAS操作不失败,该结点必定被加到同步队列末端,从tail开始遍历可以很快便找到这个结点。
private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
}
 

checkInterruptWhileWaiting()和transferAfterCancelledWait()

private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
}

final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            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.
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
}
 checkInterruptWhileWaiting()检查线程是否被中断。没被中断,返回0。线程被中断,则调用transferAfterCancelledWait()。如果线程在被唤醒前中断,返回THROW_IE(-1);如果线程在被唤醒后中断,返回REINTERRUPT(1)。

 

transferAfterCancelledWait()值得细细品味。

在条件等待被取消之后,transferAfterCancelledWait(),将结点转移到同步队列上去,其实就是将结点的状态由Node.CONDITION修改为0,然后将该结点加入同步队列。很明显,这时候结点的状态是Node.CONDITION,也就是说它的线程还没被唤醒,所以返回true,从而checkInterruptWhileWaiting()返回THROWIE。

唤醒线程和取消线程的操作都会通过CAS操作修改该结点的状态信息。如果唤醒线程操作失败了,就会将条件队列的结点转移到同步队列上去(上面提及的情形)。如果取消线程的操作失败了,它就会中止结点转移操作,然后等待重新获得锁。但是被取消的条件等待不能立刻重新获得锁,除非该结点被成功地插入同步队列。因此,我们需要自旋等待结点成功插入同步队列。这里我们通过Thread.yield()来自旋。自旋结束,返回false。然后,checkInterruptWhileWaiting()返回REINTERRUPT。

 

reportInterruptAfterWait()

这个方法InterruptedMode来操作。如果是THROW_IE(线程被唤醒前中断),抛出InterruptedException。如果是REINTERRUPT(唤醒线程后再中断),则中断当前线程。

private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
}

 除了可中断的等待wait()之外,还有不可中断的awaitUninterruptibly(),可中断的、支持超时退出的awaitNanos()、awaitUntil()、await(long time, TimeUnit unit)。理解了可中断的await()之后,这几个await操作就很容易理解了,具体实现看代码吧。

 

signal()方法

 

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
}

private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
}

 signal()方法将条件队列中等待时间最长的线程转移到同步队列。首先检查是不是当前线程独占模式下持有锁,如果不是,则抛出IllegalMonitorStateException。ConditionObject的每个non-waiting方法,例如signal(), signalAll(), getWaitingThreads(), getWaitQueueLength(), hasWaiters(),都需要先调用isHeldExclusively()方法检查一下。然后,通过doSignal()将条件队列的第一个结点出队列,将它转移到同步队列。如果转移操作失败,而且条件队列中还有下一个结点,则继续尝试转移下一个结点,直到转移操作完成或者队列已经没有其他结点为止。

 transferForSignal()方法:

1. 如果不能通过CAS操作将结点的waitStatus修改成0,则转移失败,返回false。

2. 通过CAS操作将结点的waitStatus修改成0之后,将结点插入同步队列,并尝试将当前结点的前继结点的waitStatus设为Node.SIGNAL,表示当前线程正在等待唤醒。如果尝试失败了,或者线程被取消了,则通过unpark()唤醒当前线程。这种情况下其前继结点的waitStatus可能是不对的,但这只是暂时的,不会有什么影响。

3. 返回true表示转移成功。

final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
}

 

signalAll()

 

signalAll()将条件队列中的所有等待当前条件的结点转移到同步队列去。实现的过程是在doSignalAll()中依次将每个结点出队列,然后调用transferForSignal()方法来实现的。

public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
}
private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值