通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式

1. 重入锁的概念与作用

      reentrant 锁意味着什么呢?简单来说,它有一个与获取锁相关的计数器,如果已占有锁的某个线程再次获取锁,那么lock方法中将计数器就加1后就会立刻返回。当释放锁时计数器减1,若计数器不为0,说明线程仍然占有锁;若计数器值为0,线程才会真正释放锁。

     可重入锁可以避免同一个线程嵌套(或者说递归)获取锁时的死锁现象。

      考虑下面这样一种情况

public class LockAnalysis {
	private Lock l = new ReentrantLock();
	public void funA(){
		l.lock();
		System.out.println("funA do something");
		l.unlock();
	}
	public void funB(){
		l.lock();
		System.out.println("funB do something");
		funA();
		l.unlock();
	}
}

       如果不是可重入锁,那么线程调用这个类的对象的funB方法时就会导致死锁现象。

       可重入锁的好处是,线程当前的操作需要加锁时,直接加锁即可,不需要考虑已加锁的代码块中是否又进行了加锁的操作。

      如果不是可重入锁,那么线程调用这个类的对象的funB方法时就会导致死锁现象。

       可重入锁的好处是,线程当前的操作需要加锁时,直接加锁即可,不需要考虑已加锁的代码块中是否又进行了加锁的操作。

2. ReentrantLock的内部结构

      内部类

Sync extends AbstractQueuedSynchronizer
NonfairSync extends Sync
FairSync extends Sync

       Sync继承了AbstractQueuedSynchronizer,并依据ReentrantLock的语义实现了相关方法,其它两个内部类分别表示公平锁和非公平锁所对应的同步队列,它们主要是在tryAcquire方法和lock方法的实现上采取了不同的策略,以符合公平锁和非公平锁的语义。

      重要数据成员

private final Sync sync;

       如果构造的是公平锁,sync就引用FairSync的对象,如果构造的是非公平锁sync就是NonfairSync对象的引用。

      构造函数

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

       无参数时,默认为非公平锁。有参数时,若参数为false,则为公平锁。

4. AQS的内部结构

        锁的获取过程实际上对应了AQS对状态的改变过程。现在我们就要对AQS类进行一个简要的介绍。在本文中做如下规定:未能获得锁的线程会进入队列中排队获取锁,我们称这个队列为等待(锁的)队列。线程调用Condition对象方法的await方法会阻塞,阻塞的线程会进入一个队列中等待其它线程调用signal方法唤醒,我们称这个队列为条件队列。AbstractQueuedSynchronizer简称为AQS。

      重要的数据成员

private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;

         state:对于锁而言它表明了锁的状态,0表示没有线程占有锁;非0表示已有线程占有锁,非0值表示可重入次数。

        未能获取锁的线程就进入队列进行等待,数据成员head和tail 表示等待队列的头和尾。

      内部类Node

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    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
     */
    static final int PROPAGATE = -3;
    volatile int waitStatus;

    volatile Node prev;
    volatile Node next;
    volatile Thread thread;

    Node nextWaiter;
    ……//其它暂时省略
}

         内部类Node表示了等待队列中的节点,Node中的thread是因未获取锁而等待的线程的引用。prev 和next分别指向了前一个节点和后一个节点。有时候在本文中线程和节点是同一个意思。

        image

                                  AQS队列的示意图

         头节点:它是一个哑节点,它的下一个节点开始才表示因未能获取锁而处于等待锁的节点。

         准确的说队列是由prev引用串接在一起的单向链表,节点中next引用只是一个辅助作用,在大多数情况下可以根据next找到当前节点的下一个节点。

         重点要介绍的是waitStatus,它表示节点的状态,它有五种取值

0

新创建的节点、出列的节点、队尾的节点、刚从条件队列中进入等待队列中的节点,都处于这种状态

CANCELLED = 1

表示当前节点表示的线程因超时或者被中断而处于取消的状态。处于取消状态的节点会从队列中移除,并从获取锁的方法中返回(对于可中断获取锁的方法是以抛出异常的方式返回)

SIGNAL = -1

表示当前节点出列时它的下一个节点需要唤醒

CONDITION = -2

表示当前节点位于条件队列中

PROPAGATE = -3

表示共享模式下,若当前节点被唤醒,它的下一个节点也可以被唤醒

          nextWaiter 有两个作用,一个是指明了AQS是共享模式还是独占模式(用SHARED    和      EXCLUSIVE两种值来区分);另一个作用是用于条件队列的节点指针。

        内部类ConditionObject

public class ConditionObject implements Condition, java.io.Serializable {
    ……
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
    ……//其它暂时省略
}

          ConditionObject类的内部实际上维护了一个条件队列,firstWaiter和lastWaiter表示了队列的头和尾。一个锁仅有一个等待队列,但可以对应多个条件队列(当然一个节点不能同时位于条件队列和等待队列中,也不能同时位于多个条件队列中)。由于ConditionObject是个AQS的内部类,正好满足多个条件队列对应一个等待队列,这可以看做内部类特性的一个经典应用。

         分析ReentrantLock中AQS的工作原理需要把握几点

          1. 任何时候都有可能有多个线程来竞争获取锁

          2. 任何时候都有可能有多个线程竞争入列

          3. 唤醒的线程不一定能立刻运行,可能位于就绪状态

          4. 线程随时可以由运行态转变为就绪态

          5. 锁负责状态的定义

          6. AQS负责队列的维护

5. 非公平不可中断锁的获取

public void lock() {
    sync.lock();
}

      非公平不可中断锁的获取调用了ReentrantLock类中的lock方法,实际上内部调用了NonfairSync类的lock方法。

final void lock() {
    if (compareAndSetState(0, 1)) //快速尝试获取锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

        NonfairSync类内部的lock方法首先尝试快速获取锁(而不考虑是等待队列是否有中节点还在等待获取锁,这是非公平语义的体现),如果成功直接返回,如果失败则调用了AQS类的acquire方法。

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

         tryAcquire的作用是尝试获取锁,获取成功,lock方法返回,线程继续执行;获取失败,调用addWaiter创建一个新的节点到等待队列中。acquireQueued的作用是确保当前线程阻塞后能被唤醒。

       注意:这里调用的不是AQS中的tryAcquire,而是调用了被NonfairSync类覆盖的tryAcquire方法。而NonfairSync类中tryAcquire实际上又调用了Sync类中的nonfairTryAcquire方法。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

对于非公平锁,上述代码才是tryAcquire的核心,我们现在对它进行简要的分析。

1. 判断当前状态(AQS的state字段)是否为0,

         1.1若为0,尝试用原子类操作将其置为1(任何时候都有可能有多个线程来竞争获取锁,所以必须使用原子类操作,这里也体现了锁的非公平特性,即未入列就可以尝试获取锁)。

                1.1.1成功:说明已获取锁,通过setExclusiveOwnerThread将占有锁的线程标记当前线程,然后返回true,ture说明获取锁成功。

                1.1.2 失败:返回false,说明同时有其它线程也来获取锁,并且当前线程获取锁失败。

          1.2 若为非0:判断要获取锁的线程是否是当前线程(可重入锁语义的实现)。

                1.2.1 是:state = state + 1 (即重入次数加1),然后返回true,ture说明获取锁成功。

                1.2.2 否:返回false,说明已有线程占有锁,当前线程获取锁失败了。

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,它的主要作用就是将等待的线程入列

       1. 构造一个节点,节点的nextWaiter的值为EXCLUSIVE(ReentrantLock中都是这个值,它表示了独占模式,即同时只能有一个线程拥有锁)

       2. 判断队列的尾节点是否为空

            2.1 否:通过原子操作入列,注意pred.next = node 不是原子操作不能保证入列后立刻被执行(也就是说如果一个节点的next值为null不能说明它后面没有节点,next不为null说明它一定有后继节点)。然后返回新构造的节点的引用,程序结束。

       3. 调用enq方法。

       4. 返回新构造的节点的引用

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方法。

      1. 判断当前队列的尾部是否为null,这里再次判断的目的就是考虑到多个线程会可能会先后执行enq这段程序,只有一个线程新创建的节点能作为尾节点,其它线程建立的节点都会被垃圾回收线程回收。

          1.1 是:创建一个节点,尝试通过原子操作将其作为队尾(此时它也是队列的头部)。

                1.1.1 成功:说明当前线程为队列设置了尾节点,回到步骤1

                1.1.2 失败:说明其它线程已为队列设置了尾节点,回到步骤1

          1.2 否:尝试入列

                1.2.1 成功:结束

                1.2.2 失败:说明有其它线程也在入列,发生了碰撞,并且当前线程竞争失败了。回到步骤1,再来一次循环。

        以上就是addWaiter的代码分析,现在我们再来分析一下acquireQueued方法的代码分析。

        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); // 不可中断锁中永远不会被执行
    }
}

        如果入列后发现当前节点的上一个节点是头节点就会调用setHead(node),它的作用是当节点成功获取锁以后,将当前作为头节点,上一个头节点会出列。注意头节点的更改是在成功获取锁之后,而不是在释放锁的时候。这样做的目的是考虑到非公平锁状态下,当前节点会和未入列的节点(调用nonfairTryAcquire方法中的compareAndSetState(0, acquires)语句)竞争获取锁,当未入列的节点获取到锁时,队列的头节点应该保持不变。

        p == head && tryAcquire(arg) 的作用:如果当前节点的上一个节点是头节点,当前线程要再次尝试获取锁。这样的原因,就是保证唤醒过程不会出现死链的情况。为了解释的清楚一下,我现在不得不把unlock的核心代码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;
}

        从释放锁的代码可以看出,如果头节点的waitStatus为0时,它不会唤醒头节点之后的下一个节点。

         我们现在可以举个例子,假设这样一种情况,线程a已占有锁,b,c,d线程因锁已被占有而竞争入列,a在成功释放锁后,即tryRelease(arg)返回true时,b,c,d线程还没有完成头节点的设置(原谅这三个肉肉的线程,由于调度的原因,他们动作比较慢)。这时a线程以为没有线程需要它唤醒,即unparkSuccessor(h)不会执行,它就拍拍屁股走了。此时三个线程才入列完毕(head<-b<-c<-d),注意这个时候其实锁时空闲的,如果这个时候b把自己阻塞了,那有整个等待队列中的节点都不会被唤醒。所以,某个节点发现自己的上一个节点是头节点时,还要再次尝试获取锁,如果失败,要确定头节点的waitStatus必须为SIGNAL然后再次获取锁(这是为了防止将头节点的waitStatus设置为SIGNAL之前,锁恰好被释放),又失败了才能安心的阻塞自己(调用parkAndCheckInterrupt方法中的LockSupport.park(this)阻塞自己)。

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的作用:当前线程就要把自己阻塞了,在阻塞之前务必要保证前一个节点成功释放锁后,会把当前线程唤醒。不可中断锁中节点的状态不会有waitStatus>0的情况,因此我们将不会执行的代码删除。当shouldParkAfterFailedAcquire返回true时,说明可以放心阻塞当前线程了,这时就会调用parkAndCheckInterrupt()方法来阻塞当前线程。当该节点被唤醒时,会继续从parkAndCheckInterrupt()方法中的下一条语句继续执行。

       获取锁过程的几点说明:

       (1) 如果在没有其它线程占有锁的情况下成功获取锁,则该线程不会进入队列

       (2) 节点的出列(也就是头节点的改变)是在成功获取锁之后,而不是释放锁的时候

       (3) cancelAcquire(node)在不可中断锁中不会执行

6. 可中断锁的获取

       可中断锁响应中断只有两个时刻,一个是未入列之前调用acquireInterruptibly方法时,另一个是被唤醒后从parkAndCheckInterrupt方法中返回的时候。如果线程调用lockInterruptibly抛出异常,线程就会从lockInterruptibly方法中返回,并捕获异常。

        lockInterruptibly调用了AQS的acquireInterruptibly,它实际上又调用了doAcquireInterruptibly

private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
         doAcquireInterruptibly方法的代码基本和acquireQueued类似,区别就在于当线程检测到自己的中断标志位被设置后(在parkAndCheckInterrupt方法中实现)会抛出异常 InterruptedException,使得finally块中的cancelAcquire方法得到执行。
private void cancelAcquire(Node node) {
    if (node == null)
        return;
    node.thread = null;

    
   
   
   
    
    
    
         Node pred = node.prev;
    
     
    
    
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    
   
   
   
    node.waitStatus = Node.CANCELLED; 
    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {

    
   
   
           
     
    
    
    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
    }
}

        代码的基本思路就是,从这个队列中删头节点的下一个节点(也就是当前线程自身对应的节点),并唤醒下下一个节点,因为线程一旦入列并处于等待状态,只有被唤醒以后才能响应中断,而被唤醒的前提时节点是必须位于头节点的下一个节点(前面的节点都以陆续出列)。

        cancelAcquire方法是为了可中断锁和超时锁的取消操作共同设计的,可中断锁中没有用到的代码都以横线的方式删除了。

7. 超时可中断锁的获取

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

         deadline表示了截止时间,nanoTimeout表示了尝试获取锁剩的余时间,当nanoTimeout大于一个阀值时(这主要是考虑到park操作和unpark操作所耗费的时间,该值由spinForTimeoutThreshold表示),当前线程才会调用LockSupport.parkNanos(this, nanosTimeout)将自己阻(阻塞的时间为nanosTimeout),否则继续执行续循环体。

        可以看出获取锁给定的时间在执行的时候不是一个精确的时间,实际上很可能会大于获取锁所规定的时间。不是一个精确的时间有两方面的原因,一个是由于系统的调度使得线程由运行态转为就绪态,而处于就绪态的时间不固定;另一个是执行park和unpark方法的代码需要的时间无法精确给出。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    …………
    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;
    }
    …………
}

          此时在不可中断锁中shouldParkAfterFailedAcquire被划掉的代码才会实现

private void cancelAcquire(Node node) {
    …………
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    …………
        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 {
            …………
        }
}

        基本思想就是跳过那些处于取消状态的节点,如果取消的节点是头节点的下一个节点,则将下下个节点唤醒。与可中断锁不同,这个时候的才可能有多个线程同时执行cancleAcquire方法,划掉的代码才可能会被执行。

8.锁释放代码的分析

public void unlock() {
    sync.release(1);
}

        释放锁的代码实际上调用了AQS的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;
}

        代码的主要功能:1. tryRelease释放锁     2. unparkSuccessor唤醒头节点的下一个节点

        上面代码中最重要就是tryRelease方法。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

         这里调用的是Sync类中的的tryRelease方法。对于可重入锁,每执行一次unlock方法,可重入次数减1,当state的值为0时,才真正释放了锁。

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);
}

          unparkSuccessor传入的参数是头结点,

         当头节点的next值为null时,不能说明队列为空,而要从后往前寻找需要唤醒的节点。我们将当前节点称为A,A的前一个节点称为B。当节点A入列时,无法对当前节点的prev和前一个节点B的next值同时进行原子操作。有可能正要对B节点的next引用赋值时,线程发生了调度,导致前B节点的next值为null,但实际上A节点已入列,并且位于B节点的后面。

         被唤醒的线程从parkAndCheckInterrupt()继续执行。

9. Condition的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方法前必须要获取锁。我们现在await方法阻塞当前线程之前的代码,唤醒后执行的代码我们用删除线划掉。

        如果在线程在调用await方法之前就被中断了,那么await会直接抛出异常,此时当前线程没有执行释放锁的状态。

         一个线程调用await方法,说明这个线程必定处于运行(或就绪状)态,处于运行态(或就绪态),那么它必定不会在等待队列中,也没有与之对应节点。所以addConditionWaiter内部是新建一个节点,并加入到条件队列中。

          fullyRelease 方法的作用是释放锁,对于可重入锁,释放的过程是将AQS的status值(可重入次数)存储在savedState变量中,然后通过原子类操作将status的值直接更改为0。而不是像unlock方法那样依次递减为0。

          isOnSyncQueue 判断是否在等待队列中,显然此时不在等待队列中(而位于条件队列中),至于为什么要进行这个判断,还没有彻底理解。

         最后线程调用LockSupport.park(this)将自己阻塞。

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

        当其它线程调用这个条件队列的signal方法时(调用signal方法时前必须获取锁),条件队列将头节点从条件队列中取出,然后加入到等待队列中。signal方法主要调用了dosignal方法。

   
  
  
  
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;
}

        加入到等待队列主要由transferForSignal方法实现,首先将节点的状态由CONDITION修改为0,然后调用enq方法入列(注意enq方法返回的是当前节点入列后的前一个节点),如果等待队列中已入列的前一个节点的处于取消状态,要将已入列的节点唤醒(这是为了防止死链);否则就要等到等待队列中该节点之前的已入列的节点依次出列(或者被取消)才能被唤醒。被唤醒后继续回到await中执行(被唤醒的前提是位于条件队列的队首)。

         下面的代码表示唤醒后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);
}

        检查位于条件队列中的时候是否被中断,如果是,跳出循环。否则判断循环条件isOnSyncQueue(node),显然,singal方法再唤醒前已将队首节点加入到等待队列中,所以不满足循环条件。执行acquireQueued(node, savedState),这个方法前面分析过,如果获取锁失败,则线程会在这里被阻塞,直到该节点成为了头节点的下一个节点。不同的是若果获取锁成功,这里是将status的值直接更新为可重入次数(就是前面保存的savedState的值),然后从await方法中返回,而不是像重入加锁操作那样每次累加1。

参考博客

[1] http://ifeve.com/jdk1-8-abstractqueuedsynchronizer/

[2] http://www.tuicool.com/articles/RJ3Eza2

[3] http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html


       









 




 



你可能感兴趣的:(通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式)