JAVA并发编程学习笔记之AQS源码分析

同步状态

AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下:

[java]  view plain copy print ?
  1. static final int CANCELLED =  1;  
  2. static final int SIGNAL    = -1;  
  3. static final int CONDITION = -2;  
  4. static final int PROPAGATE = -3;  
下面解释一下每个值的含义

CANCELLED:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;

SIGNAL:表示这个结点的继任结点被阻塞了,到时需要通知它;

CONDITION:表示这个结点在条件队列中,因为等待某个条件而被阻塞;

PROPAGATE:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;

0:None of the above,新结点会处于这种状态。


获取

AQS中比较重要的两个操作是获取和释放,以下是各种获取操作:

[java]  view plain copy print ?
  1. public final void acquire(int arg);  
  2. public final void acquireInterruptibly(int arg);  
  3. public final void acquireShared(int arg);  
  4. public final void acquireSharedInterruptibly(int arg);  
  5. protected boolean tryAcquire(int arg);   
  6. protected int tryAcquireShared(int arg);  
  7. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException;  
  8. public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;       
获取操作的流程图如下:

1、如果尝试获取锁成功整个获取操作就结束,否则转到2. 尝试获取锁是通过方法tryAcquire来实现的,AQS中并没有该方法的具体实现,只是简单地抛出一个不支持操作异常,在AQS简介中谈到tryAcquire有很多实现方法,这里不再细化,只需要知道如果获取锁成功该方法返回true即可;

2、如果获取锁失败,那么就创建一个代表当前线程的结点加入到等待队列的尾部,是通过addWaiter方法实现的,来看该方法的具体实现:

[java]  view plain copy print ?
  1. /** 
  2.  * Creates and enqueues node for current thread and given mode. 
  3.  * 
  4.  * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 
  5.  * @return the new node 
  6.  */  
  7. private Node addWaiter(Node mode) {  
  8.     Node node = new Node(Thread.currentThread(), mode);  
  9.     // Try the fast path of enq; backup to full enq on failure  
  10.     Node pred = tail;  
  11.     if (pred != null) {  //队尾不为空,则添加新节点到队尾后面
  12.         node.prev = pred;  
  13.         if (compareAndSetTail(pred, node)) {  
  14.             pred.next = node;  
  15.             return node;  
  16.         }  
  17.     }  
  18.     enq(node);  
  19.     return node;  
  20. }  
该方法创建了一个独占式结点,然后判断队列中是否有元素,如果有(pred!=null)就设置当前结点为队尾结点,返回;

如果没有元素(pred==null),表示队列为空,走的是入队操作

[java]  view plain copy print ?
  1. /** 
  2.  * Inserts node into queue, initializing if necessary. See picture above. 
  3.  * @param node the node to insert 
  4.  * @return node's predecessor 
  5.  */  
  6. private Node enq(final Node node) {  
  7.     for (;;) {  
  8.         Node t = tail;  
  9.         if (t == null) { // Must initialize  //新建队列
  10.             if (compareAndSetHead(new Node()))  
  11.                 tail = head;  
  12.         } else {  //队尾不为空,则添加新节点到队尾后面
  13.             node.prev = t;  
  14.             if (compareAndSetTail(t, node)) {  
  15.                 t.next = node;  
  16.                 return t;  
  17.             }  
  18.         }  
  19.     }  
  20. }  
enq方法采用的是变种CLH算法,先看头结点是否为空,如果为空就创建一个傀儡结点,头尾指针都指向这个傀儡结点,这一步只会在队列初始化时会执行;

如果头结点非空,就采用CAS操作将当前结点插入到头结点后面,如果在插入的时候尾结点有变化,就将尾结点向后移动直到移动到最后一个结点为止,然后再把当前结点插入到尾结点后面,尾指针指向当前结点,入队成功。

3、将新加入的结点放入队列之后,这个结点有两种状态,要么获取锁,要么就挂起,如果这个结点不是头结点,就看看这个结点是否应该挂起,如果应该挂起,就挂起当前结点,是否应该挂起是通过shouldParkAfterFailedAcquire方法来判断的

[java]  view plain copy print ?
  1. /** 
  2.     * Checks and updates status for a node that failed to acquire. 
  3.     * Returns true if thread should block. This is the main signal 
  4.     * control in all acquire loops.  Requires that pred == node.prev 
  5.     * 
  6.     * @param pred node's predecessor holding status 
  7.     * @param node the node 
  8.     * @return {@code true} if thread should block 
  9.     */  
  10.    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
  11.        int ws = pred.waitStatus;  
  12.        if (ws == Node.SIGNAL)  
  13.            /* 
  14.             * This node has already set status asking a release 
  15.             * to signal it, so it can safely park. 
  16.             */  
  17.            return true;  
  18.        if (ws > 0) {  
  19.            /* 
  20.             * Predecessor was cancelled. Skip over predecessors and 
  21.             * indicate retry. 
  22.             */  
  23.            do {  
  24.                node.prev = pred = pred.prev;  
  25.            } while (pred.waitStatus > 0);  
  26.            pred.next = node;  
  27.        } else {  
  28.            /* 
  29.             * waitStatus must be 0 or PROPAGATE.  Indicate that we 
  30.             * need a signal, but don't park yet.  Caller will need to 
  31.             * retry to make sure it cannot acquire before parking. 
  32.             */  
  33.            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
  34.        }  
  35.        return false;  
  36.    }  
该方法首先检查前趋结点的waitStatus位,如果为SIGNAL,表示前趋结点会通知它,那么它可以放心大胆地挂起了;

如果前趋结点是一个被取消的结点怎么办呢?那么就向前遍历跳过被取消的结点,直到找到一个没有被取消的结点为止,将找到的这个结点作为它的前趋结点,将找到的这个结点的waitStatus位设置为SIGNAL,返回false表示线程不应该被挂起。
上面谈的不是头结点的情况决定是否应该挂起,是头结点的情况呢?

是头结点的情况,当前线程就调用tryAcquire尝试获取锁,如果获取成功就将头结点设置为当前结点,返回;如果获取失败就循环尝试获取锁,直到获取成功为止。整个acquire过程就分析完了。


释放

释放操作有以下方法:

[java]  view plain copy print ?
  1. public final boolean release(int arg);   
  2. protected boolean tryRelease(int arg);   
  3. protected boolean tryReleaseShared(int arg);   

下面看看release方法的实现过程


1、release过程比acquire要简单,首先调用tryRelease释放锁,如果释放失败,直接返回;

2、释放锁成功后需要唤醒继任结点,是通过方法unparkSuccessor实现的

[java]  view plain copy print ?
  1. /** 
  2.     * Wakes up node's successor, if one exists. 
  3.     * 
  4.     * @param node the node 
  5.     */  
  6.    private void unparkSuccessor(Node node) {  
  7.        /* 
  8.         * If status is negative (i.e., possibly needing signal) try 
  9.         * to clear in anticipation of signalling.  It is OK if this 
  10.         * fails or if status is changed by waiting thread. 
  11.         */  
  12.        int ws = node.waitStatus;  
  13.        if (ws < 0)  
  14.            compareAndSetWaitStatus(node, ws, 0);  
  15.   
  16.        /* 
  17.         * Thread to unpark is held in successor, which is normally 
  18.         * just the next node.  But if cancelled or apparently null, 
  19.         * traverse backwards from tail to find the actual 
  20.         * non-cancelled successor. 
  21.         */  
  22.        Node s = node.next;  
  23.        if (s == null || s.waitStatus > 0) {  //如果后继结点为空或者被取消了
  24.            s = null;  
  25.            for (Node t = tail; t != null && t != node; t = t.prev)  
  26.                if (t.waitStatus <= 0)  
  27.                    s = t;  
  28.        }  
  29.        if (s != null)  
  30.            LockSupport.unpark(s.thread);  
  31.    }  
1、node参数传进来的是头结点,首先检查头结点的waitStatus位,如果为负,表示头结点还需要通知后继结点,这里不需要头结点去通知后继,因此将该该标志位清0.

2、然后查看头结点的下一个结点,如果下一个结点不为空且它的waitStatus<=0,表示后继结点没有被取消,是一个可以唤醒的结点,于是唤醒后继结点返回;如果后继结点为空或者被取消了怎么办?寻找下一个可唤醒的结点,然后唤醒它返回。

这里并没有从头向尾寻找,而是相反的方向寻找,为什么呢?

因为在CLH队列中的结点随时有可能被中断,被中断的结点的waitStatus设置为CANCEL,而且它会被踢出CLH队列,如何个踢出法,就是它的前趋结点的next并不会指向它,而是指向下一个非CANCEL的结点,而它自己的next指针指向它自己。一旦这种情况发生,如何从头向尾方向寻找继任结点会出现问题,因为一个CANCEL结点的next为自己,那么就找不到正确的继任接点。

有的人又会问了,CANCEL结点的next指针为什么要指向它自己,为什么不指向真正的next结点?为什么不为NULL?

第一个问题的答案是这种被CANCEL的结点最终会被GC回收,如果指向next结点,GC无法回收。

对于第二个问题的回答,JDK中有这么一句话: The next field of cancelled nodes is set to point to the node itself instead of null, to make life easier for isOnSyncQueue.大至意思是为了使isOnSyncQueue方法更新简单。isOnSyncQueue方法判断一个结点是否在同步队列,实现如下:

[java]  view plain copy print ?
  1. /** 
  2.  * Returns true if a node, always one that was initially placed on 
  3.  * a condition queue, is now waiting to reacquire on sync queue. 
  4.  * @param node the node 
  5.  * @return true if is reacquiring 
  6.  */  
  7. final boolean isOnSyncQueue(Node node) {  
  8.     if (node.waitStatus == Node.CONDITION || node.prev == null)  
  9.         return false;  
  10.     if (node.next != null// If has successor, it must be on queue  
  11.         return true;  
  12.     /* 
  13.      * node.prev can be non-null, but not yet on queue because 
  14.      * the CAS to place it on queue can fail. So we have to 
  15.      * traverse from tail to make sure it actually made it.  It 
  16.      * will always be near the tail in calls to this method, and 
  17.      * unless the CAS failed (which is unlikely), it will be 
  18.      * there, so we hardly ever traverse much. 
  19.      */  
  20.     return findNodeFromTail(node);  
  21. }  
如果一个结点next不为空,那么它在同步队列中,如果CANCEL结点的后继为空那么CANCEL结点不在同步队列中,这与事实相矛盾。

因此将CANCEL结点的后继指向它自己是合理的选择。


参考资料:

The java.util.concurrent Synchronizer Framework
java.util.concurrent 包下的 Synchronizer 框架

共享模式与独占模式

AQL的内部队列采用的是CLH队列锁模型,CLH队列是由一个一个结点(Node)构成的。Node类中有两个常量SHARE和EXCLUSIVE,顾名思义这两个常量用于表示这个结点支持共享模式还是独占模式,共享模式指的是允许多个线程获取同一个锁而且可能获取成功,独占模式指的是一个锁如果被一个线程持有,其他线程必须等待。多个线程读取一个文件可以采用共享模式,而当有一个线程在写文件时不会允许另一个线程写这个文件,这就是独占模式的应用场景。

[java]  view plain copy print ?
  1. /** Marker to indicate a node is waiting in shared mode */  
  2. static final Node SHARED = new Node();  
  3.   
  4. /** Marker to indicate a node is waiting in exclusive mode */  
  5. static final Node EXCLUSIVE = null;  
  6.   
  7. final boolean isShared() {  
  8.     return nextWaiter == SHARED;  
  9. }  
以上代码是两种模式的定义,可以通过方法isShared来判断一个结点处于何种模式。


共享模式下获取锁

共享模式下获取锁是通过tryAcquireShared方法来实现的,其流程大至如下:

AQS类方法中方法名不含shared的默认是独占模式,前面提到子类需要重写tryAcquire方法,这是在独占模式下。如果子类想支持共享模式,同样必须重写tryAcquireShared方法,线程首先通过tryAcquireShared方法在共享模式下获取锁,如果获取成功就直接返回,否则执行以下步骤:
[java]  view plain copy print ?
  1. /** 
  2.  * Acquires in shared uninterruptible mode. 
  3.  * @param arg the acquire argument 
  4.  */  
  5. private void doAcquireShared(int arg) {  
  6.     final Node node = addWaiter(Node.SHARED);  
  7.     boolean failed = true;  
  8.     try {  
  9.         boolean interrupted = false;  
  10.         for (;;) {  
  11.             final Node p = node.predecessor();  
  12.             if (p == head) {  
  13.                 int r = tryAcquireShared(arg);  
  14.                 if (r >= 0) {  
  15.                     setHeadAndPropagate(node, r);  
  16.                     p.next = null// help GC  
  17.                     if (interrupted)  
  18.                         selfInterrupt();  
  19.                     failed = false;  
  20.                     return;  
  21.                 }  
  22.             }  
  23.             if (shouldParkAfterFailedAcquire(p, node) &&  
  24.                 parkAndCheckInterrupt())  
  25.                 interrupted = true;  
  26.         }  
  27.     } finally {  
  28.         if (failed)  
  29.             cancelAcquire(node);  
  30.     }  
  31. }  
1、创建一个新结点(共享模式),加入到队尾,这个过程和独占模式一样,不再重复;
2、判断新结点的前趋结点是否为头结点,如果不是头结点,就将前趋结点的状态标志位设置为SIGNAL,当前线程可以安全地挂起,整个过程结束;
3、如果它的前趋是头结点,就让前趋在共享模式下获取锁,如果获取成功,把当前结点设置为头结点;
4、设置为头结点之后,满足释放锁条件就阻塞等待释放锁。
满足释放锁的条件为:允许传播或者需要通知继任结点,或者继任结点是共享模式的结点
[java]  view plain copy print ?
  1. if (propagate > 0 || h == null || h.waitStatus < 0) {  //满足释放锁的条件为:允许传播或者需要通知继任结点,或者继任结点是共享模式的结点
  2.           Node s = node.next;  
  3.           if (s == null || s.isShared())  
  4.               doReleaseShared();  
  5.       }  

共享模式下释放锁

这是通过方法releaseShared来实现的,整个流程如下:
1、调用子类的tryReleaseShared尝试获取锁,如果失败,直接返回;
2、如果成功调用doReleaseShared方法做后续处理,doReleaseShared方法如下:
[java]  view plain copy print ?
  1. /** 
  2.     * Release action for shared mode -- signal successor and ensure 
  3.     * propagation. (Note: For exclusive mode, release just amounts 
  4.     * to calling unparkSuccessor of head if it needs signal.) 
  5.     */  
  6.    private void doReleaseShared() {  
  7.        /* 
  8.         * Ensure that a release propagates, even if there are other 
  9.         * in-progress acquires/releases.  This proceeds in the usual 
  10.         * way of trying to unparkSuccessor of head if it needs 
  11.         * signal. But if it does not, status is set to PROPAGATE to 
  12.         * ensure that upon release, propagation continues. 
  13.         * Additionally, we must loop in case a new node is added 
  14.         * while we are doing this. Also, unlike other uses of 
  15.         * unparkSuccessor, we need to know if CAS to reset status 
  16.         * fails, if so rechecking. 
  17.         */  
  18.        for (;;) {  
  19.            Node h = head;  
  20.            if (h != null && h != tail) {  
  21.                int ws = h.waitStatus;  
  22.                if (ws == Node.SIGNAL) {  
  23.                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  
  24.                        continue;            // loop to recheck cases  
  25.                    unparkSuccessor(h);  
  26.                }  
  27.                else if (ws == 0 &&  
  28.                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  
  29.                    continue;                // loop on failed CAS  
  30.            }  
  31.            if (h == head)                   // loop if head changed  
  32.                break;  
  33.        }  
  34.    }  
这个方法就一个目的,就是把当前结点设置为SIGNAL或者PROPAGATE,如果当前结点不是头结点也不是尾结点,先判断当前结点的状态位是否为SIGNAL,如果是就设置为0,因为共享模式下更多使用PROPAGATE来传播,SIGNAL会被经过两步改为PROPAGATE:
compareAndSetWaitStatus(h, Node.SIGNAL, 0)
compareAndSetWaitStatus(h, 0, Node.PROPAGATE)
为什么要经过两步呢?原因在unparkSuccessor方法:
[java]  view plain copy print ?
  1. private void unparkSuccessor(Node node) {  
  2.     int ws = node.waitStatus;  
  3.     if (ws < 0)  
  4.         compareAndSetWaitStatus(node, ws, 0);  
  5.         ......  
  6. }  
如果直接从SIGNAL到PROPAGATE,那么到unparkSuccessor方法里面又被设置为0:SIGNAL--PROPAGATE---0----PROPAGATE
对头结点相当于多做了一次compareAndSet操作,其实性能也殊途同归啦!

闭锁(CountDownLatch)

闭锁是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
闭锁有几个重要的方法:
[java]  view plain copy print ?
  1. public void await() throws InterruptedException;  
  2. public void countDown();  
其中await方法使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,如果锁存器为0方法立即返回,一开始锁存器不会为0,当调用countDown方法之后锁存器会减少,当锁存器减少到0时,await方法就会返回。现在看看await方法的实现:
[java]  view plain copy print ?
  1. public void await() throws InterruptedException {  
  2.     sync.acquireSharedInterruptibly(1);  
  3. }  
不出所料,闭锁的await方法正是使用的共享模式的AQS,acquireSharedInterruptibly和acquireShared方法类似,只不过会先响应中断。也就是当有多个线程调用await方法时,这些线程都被阻塞到了doAcquireShared方法的以下地方:
[java]  view plain copy print ?
  1. if (shouldParkAfterFailedAcquire(p, node) &&  
  2.                    parkAndCheckInterrupt())  
  3.                    interrupted = true;  
前面看到doAcquireShared里面有一个for循环,退出for循环的唯一方式是要tryAcquireShared方法返回值大于0,下面看看tryAcquireShared的方法在闭锁中的实现:
[java]  view plain copy print ?
  1. public class CountDownLatch {  
  2.     private static final class Sync extends AbstractQueuedSynchronizer  {  
  3.         Sync(int count) {  
  4.             setState(count);  
  5.         }  
  6.         ......  
  7.     }  
  8.       
  9.     private final Sync sync;  
  10.           
  11.     protected int tryAcquireShared(int acquires) {  
  12.         return (getState() == 0) ? 1 : -1;  
  13.     }  
  14.     ......  
  15. }  
count代表是的线程数,在创建闭锁的同步器时这个count值被赋给了state,因此state肯定不为0,所以tryAcquireShared方法肯定返回-1,也就是这些线程调用await方法时tryAcquireShared都返回-1,这些线程都会阻塞在doAcquireShared的for循环里。然后这些线程依次调用countDown方法,直到最后一个线程调用完后这些线程才会退出for循环继续执行。下面看看countDown方法的实现过程:
[java]  view plain copy print ?
  1. public void countDown() {  
  2.     sync.releaseShared(1);  
  3. }  
  4.   
  5. //sync.releaseShared  
  6. public final boolean releaseShared(int arg) {  
  7.     if (tryReleaseShared(arg)) {  
  8.         doReleaseShared();  
  9.         return true;  
  10.     }  
  11.     return false;  
  12. }  
仍然不出所料,countDown方法正是调用的releaseShared方法,前面提到releaseShared会先调用tryReleaseShared方法,这是由闭锁实现的:
[java]  view plain copy print ?
  1. protected boolean tryReleaseShared(int releases) {  
  2.     // Decrement count; signal when transition to zero  
  3.     for (;;) {  
  4.         int c = getState();  
  5.         if (c == 0)  
  6.             return false;  
  7.         int nextc = c-1;  
  8.         if (compareAndSetState(c, nextc))  
  9.             return nextc == 0;  
  10.     }  
  11. }  
该方法会递减state的值,直到变为0返回false.
现在整个闭锁的执行流程很明确了:N个线程调用await阻塞在for循环里面,然后N个线程依次调用countDown,每调用一次state减1,直接state为0,这些线程退出for循环(解除阻塞)!
退出for循环时,由于头结点状态标志位为PROPAGATE,而且这些结点都是共享模式,由头结点一传播,这些结点都获取锁,于是齐头并进执行了......
共享与独占在读写锁里面也有用到,后面再分析。

参考资料:

AbstractQueuedSynchronizer 独占模式 VS 共享模式


中断

JAVA中并没有好的中断线程的方式,早期引入的Thead.stop()和Thread.resume()容易导致死锁(参考:http://docs.oracle.com/javase/6/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html),已经不推荐使用。

JAVA线程提供了协作式中断,何为协作是中断,是相对抢占式中断而言的,简单来讲就是设置一个中断标志位,不停地检查这个标志位的状态,如果检查到线程中断,就中断线程。JVM线程内部维护着一个中断标志,程序员不能直接操作这个中断标志位,只能通过线程的以下几个方法设置中断位:

[html]  view plain copy print ?
  1. public void interrupt()  
  2. public static boolean interrupted()   
  3. private native boolean isInterrupted(boolean ClearInterrupted);  
  4. public boolean isInterrupted()  

AQS中提供了支持中断的方法

[html]  view plain copy print ?
  1. private void doAcquireInterruptibly(int arg) throws InterruptedException;  
  2. private void doAcquireSharedInterruptibly(int arg) throws InterruptedException;   
  3. private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;  
这几个方法都抛出了InterruptedException,这些方法都会先出处中断异常,处理的代码如下:

[html]  view plain copy print ?
  1. if (Thread.interrupted())  
  2.     throw new InterruptedException();  
我们还看到有些方法并没有申请抛出InterruptedException,当它被中断时,设置了线程的中断位。

[html]  view plain copy print ?
  1. private static void selfInterrupt() {  
  2.     Thread.currentThread().interrupt();  
  3. }  

超时

AQS与JVM内置锁的一个不同点在于AQS中提供了超时机制,即线程在等待一定时间后会立即返回。下面以doAcquireNanos为例来分析:
[java]  view plain copy print ?
  1. private boolean doAcquireNanos(int arg, long nanosTimeout)  
  2.     throws InterruptedException {  
  3.     long lastTime = System.nanoTime();  
  4.     final Node node = addWaiter(Node.EXCLUSIVE);  
  5.     boolean failed = true;  
  6.     try {  
  7.         for (;;) {  
  8.             final Node p = node.predecessor();  
  9.             if (p == head && tryAcquire(arg)) {  
  10.                 setHead(node);  
  11.                 p.next = null// help GC  
  12.                 failed = false;  
  13.                 return true;  
  14.             }  
  15.             if (nanosTimeout <= 0)  
  16.                 return false;  
  17.             if (shouldParkAfterFailedAcquire(p, node) &&  
  18.                 nanosTimeout > spinForTimeoutThreshold)  
  19.                 LockSupport.parkNanos(this, nanosTimeout);  
  20.             long now = System.nanoTime();  
  21.             nanosTimeout -= now - lastTime;  
  22.             lastTime = now;  
  23.             if (Thread.interrupted())  
  24.                 throw new InterruptedException();  
  25.         }  
  26.     } finally {  
  27.         if (failed)  
  28.             cancelAcquire(node);  
  29.     }  
  30. }  
1、首先取得当前系统时间,在循环等待的过程中,如果剩余时间<=0立即返回;
2、如果剩余时间>0,就用总时间减去一次循环耗费的时间,继续阻塞;
3、如果在这期间线程被中断,就抛出中断异常,如果有其他异常产生,就取消这次获取。

取消

取消获取的逻辑比较复杂,下面来分析一下:
[java]  view plain copy print ?
  1. private void cancelAcquire(Node node) {  
  2.       // Ignore if node doesn't exist  
  3.       if (node == null)  
  4.           return;  
  5.   
  6.       node.thread = null;  
  7.   
  8.       // Skip cancelled predecessors  
  9.       Node pred = node.prev;  
  10.       while (pred.waitStatus > 0)  
  11.           node.prev = pred = pred.prev;  
  12.   
  13.       // predNext is the apparent node to unsplice. CASes below will  
  14.       // fail if not, in which case, we lost race vs another cancel  
  15.       // or signal, so no further action is necessary.  
  16.       Node predNext = pred.next;  
  17.   
  18.       // Can use unconditional write instead of CAS here.  
  19.       // After this atomic step, other Nodes can skip past us.  
  20.       // Before, we are free of interference from other threads.  
  21.       node.waitStatus = Node.CANCELLED;  
  22.   
  23.       // If we are the tail, remove ourselves.  
  24.       if (node == tail && compareAndSetTail(node, pred)) {  
  25.           compareAndSetNext(pred, predNext, null);  
  26.       } else {  
  27.           // If successor needs signal, try to set pred's next-link  
  28.           // so it will get one. Otherwise wake it up to propagate.  
  29.           int ws;  
  30.           if (pred != head &&  
  31.               ((ws = pred.waitStatus) == Node.SIGNAL ||  
  32.                (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&  
  33.               pred.thread != null) {  
  34.               Node next = node.next;  
  35.               if (next != null && next.waitStatus <= 0)  
  36.                   compareAndSetNext(pred, predNext, next);  
  37.           } else {  
  38.               unparkSuccessor(node);  
  39.           }  
  40.   
  41.           node.next = node; // help GC  
  42.       }  
  43.   }  
1、首先取得当前结点的前趋结点,如果前趋结点也被取消直接跳过,继续向前找非取消的结点;
2、将当前结点设置为取消状态;
3、如果当前结点是队尾结点,则将当前结点从队尾移除;否则执行4;
4、找到当前结点的继任结点,前趋的next指针指向继任结点(pred->next=current->next);
5、当前结点的next指针指向自己,前面提到这一方面为了回收,一方面为了使isOnSyncQueue方法简单。

其他

AQS还提供了一些线程监控的方法:
[java]  view plain copy print ?
  1. //获取哪些线程在等待  
  2. protected final Collection getWaitingThreads();   
  3. //获取等待队列的长度  
  4. protected final int getWaitQueueLength();   
  5. //是否有线程在等待  
  6. protected final boolean hasWaiters()  
  7. //是否拥有同步器  
  8. final boolean isOwnedBy(AbstractQueuedSynchronizer sync)  
  9. //是否在同步队列中  
  10. final boolean isOnSyncQueue(Node node)  
  11. //支持共享模式的线程  
  12. public final Collection getSharedQueuedThreads()  
  13. //支持独占模式的线程  
  14. public final Collection getExclusiveQueuedThreads();  

参考资料:

Java线程中断的本质和编程原则

你可能感兴趣的:(多线程-并发)