ReentrantLock Condition源码分析

在看本文前,如果对ReentrantLock不了解的,请先看 ReentrantLock源码分析,以免有些地方无法理解。
代码的执行说明,都已注释的方式写在了代码中,如果想看某个方法的分析,直接搜索方法名即可。当然,本文中对于在ReentrantLock中详细说明过的方法以及相关分析(如将节点加入到同步队列的方法 enq,将节点挂起的方法acquireQueued,waitstatus状态说明等),在这里没有详细说明,请直接看ReentrantLock源码分析

Condition.await

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //将当前线程封装为Node节点加入到wait队列中
    Node node = addConditionWaiter();
    //在wait将项城挂起前释放锁
    //在释放锁的同时,会那单线程的重入次数State。后面再讲线程重新挂起到lock上的时候,会还原原有的state
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //判断当前节点是否是已经在同步队列上了,如果已经在同步队列上了,就不需要挂起。
    //当然,这里将线程挂起后,当线程被single唤醒,将通过这个判断跳出循环,因为在single将线程唤醒后会同时将线程Node加入到同步队列中。
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //如果Node节点是中断唤醒的,则通过这里跳出循环
        //这里需要注意的一个点是给interruptMode赋值了,interruptMode 记录了不同的中断方式(抛异常还是interupt)
        //通过后面的分析可以看到,如果Node是因为中断导致将Node加入到Lock,则将中断方式设置为抛异常。
        //也就是线程还在wait队列中时候被中断了,则对外的终端是抛异常告知。如果是在lock中被中断,则告知的方式是通过interupt方式。
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //执行到这里,说明被wait的线程已经唤醒了,并且已经将线程加入到了同步队列中。
    //这里首先通过acquireQueued将加入到同步队列中的Node节点挂起
    //一定要注意:一旦线程在这里通过acquireQueued挂起,线程将不会再继续往下执行,只有当线获取锁之后,才会从这里继续往下执行。
    //并且这里挂起的时候,会还原线程原本的重入次数savedState 。
    //如果一旦获取了锁,会再次判断是否是抛异常,如果不是,设置中断方式就是interupt的方式
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    //这里判断Node节点的nextWaiter 是否等于空。
    //通过这个判断当前节点是否还继续链接在wait队列上,如果还继续链接在wait队列上,就将其从wait队列上删除。
    //通过后面对addConditionWaiter的分析,你会看到,wait是单向队列,节点之间是通过addConditionWaiter连接的。
    //通过后面分析,你会发现,如果是中断将节点加入到了同步队列,并不会将wait队列上的节点删掉。
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    //这里根据不同中断方式,对外进行不同的中断相应
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
//这个方法的作用,就是将当前线程封装成为Node节点并加入到wait队列。
//wait是一个单向队列,firstWaiter 指向头结点,lastWaiter指向尾巴节点,节点之间通过nextWaiter链接,并且处于wait队列上的正常的节点的waitStatus 状态都为CONDITION(如果被唤醒并加入到同步队列,就会将其改为0)
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //这里判断尾节点的waitStatus是否是CONDITION,如果不是,则通过unlinkCancelledWaiters删除那些不符合条件的节点。
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //从wait队列上删除不符合条件的节点后,就将当前线程节点链接到wait队列上。
    //这里不用考虑安全问题,因为既然要调用wait必须要先获得锁。
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
//将队列上,waitStatus 不等于CONDITION的节点去掉。
//这个逻辑很简单,就是简单的链表操作,就不逐句分析了.
//遍历链表,维护了三个连续指针,中间指针一旦waitStatus 不等于CONDITION,就将其从节点上去掉。
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;
    }
}
//释放锁,这里之所以使用fullyRelease,意思就是如果一个锁多次重入,那么就需要一次性将多次重入一同释放。
//这对于ReentrantLock来说当前是很简单的,只需要将state设置成0即可。
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
    	//获取当前线程持有的state。state也代表了锁的重入次数
        int savedState = getState();
        //将原state作为参数,一次性全部释放掉。释放成功了,返回wait前持有的锁的个数
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
//判断当前节点是否已经存在于同步队列上了
final boolean isOnSyncQueue(Node node) {
	//如果已经放入了同步队列。waitStatus一定不为CONDITION,并且他的prev一定不为NULL。
	//所以满足下面的任何一个条件,说明都没有放到同步队列上。所以返回false。
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    //如果不为null,说明一定在同步队列上了,返回true。
    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.
     */
     //前面的两个if如果满足条件就立即返回了,如果都不满足,就只有遍历同步队列来逐个检查了,当然比较浪费时间。
    return findNodeFromTail(node);
}
//遍历lock逐个比较
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
//判断是否中断,如果线程没有中断,则返回0。如果中断了,则通过transferAfterCancelledWait判断是否是放入lock前发生的中断。
//如果是,则通过抛异常响应中断,如果不是,则通过interupt的方式响应中断
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
//判断是否是放入lock前发生的中断
final boolean transferAfterCancelledWait(Node node) {
	//如果当前节点的waitstatus为Node.CONDITION,则当前节点就一定还在wait队列上。因为已经发生了中断,所以就需要将其放入到同步队列。
	//放入之前,先通过CAS将waitstatus替换为0。如果替换失败了,说明其他线程正在将当前节点放入到lock。
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    	//如果替换成功了,则通过enq将节点放入到同步队列
        enq(node);
        //说明是在wait中发生的中断,则返回true
        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.
     */
     //上一步说,如果CAS替换失败,说明有其他线程正在将Node放入到同步队列中,但是不已经已经放入成功了,可能只是修改了waitstatus,还没有最终放入,所以这里如果判断还没有完全放入成功的时候,先通过死循环加yield自旋等待一会。
    while (!isOnSyncQueue(node))
        Thread.yield();
    //知道到这,说明是其他线程已经将节点放入了同步队列,说明不是在wait队列中发生的中断。
    return false;
}
//通过不同的中断方式,对外进行中断的响应
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();

Condition.signal

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //如果wait队列有被wait的节点,则进行唤醒第一个节点
    if (first != null)
        doSignal(first);
}
//唤醒wait队列上的第一个节点
private void doSignal(Node first) {
    do {
    	//判断是否队列上只有一个节点,如果是,就将lastWaiter 设置为null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        //既然要唤醒第一个节点,唤醒后,直接将第一个节点从队列上移除。
        first.nextWaiter = null;
    //通过transferForSignal将节点加入到同步队列中。如果加入失败了,则重新取出wait队列的第一个节点并唤醒。
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
//将wait节点放入到同步队列中
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
     //这里如果CAS失败,说明当前节点的状态已经不是CONDITION,说明已经被其他线程唤醒了。
     //返回false之后,外层是一个循环,会继续取出wait队列的第一个节点,继续执行唤醒操作
    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).
     */
     //将当前节点加入到同步队列,注意,这里enq返回值返回的是假如队列节点的前一个节点,也就是原队尾节点。
    Node p = enq(node);
    int ws = p.waitStatus;
    //这里的判断非常巧妙,我们知道,我们把Node节点从wait移动到同步队列,如果我们把节点线程唤醒,是没有问题的。
    //因为唤醒后执行到await中的acquireQueued的时候,会被重新挂起,但是这样比较耗费性能,是没有必要的。
    //所以这里进行了判断,如果移入到同步队列后,发现原尾节点的状态大于0,或者将尾节点的状态改为SIGNAL的时候失败了,才会唤醒。并在acquireQueued中重新整理同步队列并重新挂起。
    //这里不挂起是没有问题的,因为在acquireQueued挂起前判断,如果当前节点是第一个节点,会直接获取锁。如果中断唤醒了,或继续从await挂起的地方继续执行,会继续在acquireQueued的地方重新挂起。
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

Condition.signalAll

public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //拿到wait队列,执行doSignalAll
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
//理解了signal,理解这里的doSignalAll就很简单了。
//就是遍历wait队列上的节点逐个顺序取出放入到同步队列中。
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章