Condition源码分析

Condition和wait/notify的区别        

        Condition接口定义如下,await/signal其功能和wait/notify类似。

        多线程之间的协作通信,可以使用wait/notify、notifyAll实现。wait/notify必须和synchronized一起使用。线程因wait阻塞,可以通过notify唤醒一个线程,但唤醒哪一个线程存在不确定性,也可以通过notifyAll唤醒所有的线程,总之无法做到精准的控制。在生产者和消费者模式中,生产者线程通过notify/notifyAll唤醒消费者线程,可能使得生产者线程被唤醒,造成不必要的线程上下文切换。同时wait(long timeout)方法可能是通过notify唤醒也可能是超时唤醒,但是无法通过这方法区分。Condition就可以解决这两个问题。

Condition与Lock的关系

        在Lock的接口中,有一个与Condition相关的接口,Condition必须和Lock一起使用。所以Condition的实现也是Lock的一部分,所有的Condition都是从Lock中构造出来的。

        互斥锁和读写锁中Condition的构造:

public class ReentrantLock implements Lock, java.io.Serializable {
    ...
    public Condition newCondition() {
        return sync.newCondition();
    }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    }

}

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    ...
    public static class ReadLock implements Lock, java.io.Serializable {
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
    }
    
    public static class WriteLock implements Lock, java.io.Serializable {
        public Condition newCondition() {
            return sync.newCondition();
        }
    }

    abstract static class Sync extends AbstractQueuedSynchronizer {
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    }

}

   读写锁中的ReadLock是不支持Condition,读写锁的写锁和互斥锁都支持Condition。 互斥锁和读写锁虽然使用的是各自的内部类Sync,但他们的内部类Sync都继承自AQS,ConditionObject是AQS的内部类实现了Condition接口。因此sync.newCondition()最终都是AQS中的ConditionObject对象。

Condition实现原理
public class ConditionObject implements Condition, java.io.Serializable {
    ...
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    ...
}

        在Condition的实现中也有队列,说明一个Condition对象可以阻塞多个线程。为何便于区分,我们将Condition中的队列称为条件等待队列,AQS中的队列称为同步队列。

await()使用示例
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
 
try {
    lock.lock();
    while (条件不满足) {
        try {
            condition.await(); // 等待条件满足或被中断
        } catch (InterruptedException e) {
            // 处理中断
            Thread.currentThread().interrupt(); // 清除中断状态并重新中断线程
            break; // 或其他中断处理逻辑
        }
    }
} finally {
    lock.unlock();
}
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)) {
        //如果不在同步队列中,阻塞自己
        //初始的时候,Node只在Condition的队列里,而不在AQS的队列里。    
        //但执行signal操作的时候,会放进AQS的同步队列
        LockSupport.park(this);
        //此时,线程被唤醒,检查park期间是否发生过中断,
        //如果是被中断唤醒跳出while循环
        //interruptMode=1表示退出await时重置中断信号 ,interruptMode=-1表示退出时抛异常
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //waite被中断或signal唤醒后,此时节点已经从条件等待队列移动到同步队列中
    //在同步队列中排队等待从新获取锁,将锁恢复为原来的状态。
    //获取不到锁,则会进入阻塞状态,直到获取锁。
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) 
        //从队列中清除已经被取消的节点
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
/**
 *向条件等待队列的队尾添加节点
 *条件等待队列是一个单向队列
 */
private Node addConditionWaiter() {
    //对链表的操作不需要执行CAS操作,线程是安全的,
    //因为只有先lock获取锁才能使用Condition await。
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //如果条件等待队列的队尾节点不是CONDITION状态(也就是处于取消状态),
    //需要重新整理一下等待队列,将取消的节点从队列中清除
    //并且重新得到有效的队尾节点。
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        //头节点代表一个线程,与互斥锁不同,互斥锁头节点是信号节点,不代表线程
        firstWaiter = node;
    else
        //入队时使用nextWaiter进行单向关联,与同步队列有差异,
        //同步队列使用prev、next进行双向关联
        //说明条件等待队列是单向队列
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

/**
 *彻底释放锁
 *如果锁存在多次重入,一次性释放,重入次数归零
 */
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;
    }
}

//尝试释放锁,释放锁成功后尝试唤醒同步队列中头节点的下一个节点代表的线程
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//检测线程在park期间是否收到过中断信号,如果未发生中断返回0,如果发生了中断返回1或者-1。
//当线程从park中醒来时,有两种可能:一种是其他线程调用了unpark,
//另一种是收到中断信号。
//THROW_IE表示线程signal()前被中断,REINTERRUPT表示线程既signal()又被中断
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}

//进入到此方法说明线程接收到了中断信号
//根据接收到中断信号,有没有发生signal,进行处理。
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        //CAS成功表明线程接收到中断信号,但没有调用signal()
        //需要把节点加入到同步队列当中,这样节点才有机会去竞争锁
        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.
         */
    //CAS失败,已经接收到中断信号也调用了signal()将node状态改变,等待signal()操作将节点
    //加入到同步队列。
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

1、判断线程是否被中断,如果被中断则抛出InterruptedException。

2、将当前节点加入到条件等待队列的尾部。

3、释放锁并尝试唤醒同步队列中的线程。

4、新加入的节点一定不在同步队列中,LockSupport.park阻塞自己。

5、线程被唤醒(其他线程调用了unpark,或者是收到中断信号),检查park期间是否被中断。

        5.1 线程被中断,进一步检查是否收到signal信号,没有则将节点加入到同步队列中,此时interruptMode=THROW_IE=-1,有则等待signal的线程将节点加入到同步队列,此时interruptMode=REINTERRUPT=1。 直接跳出循环。

        5.2 没有被中断,执行下一次循环,检查当前节点是否在同步队列中。

6、线程被唤醒后,尝试恢复锁状态,重新获取锁。

7、如果当前节点的nextWaiter!=null,清理条件等待队列中已经被取消的节点。

8、interruptMode=THROW_IE=-1 抛出中断异常,interruptMode=REINTERRUPT=1 重置中断标志。

小结:

1、调用await前必须先获取锁。

2、条件等待队列是单向队列,且头节点代表线程。

3、调用await,线程会先释放锁并将代表线程的节点加入到条件等待队列的队尾并阻塞。

4、唤醒后,需要恢复锁状态,重新获取锁。

awaitUninterruptibly源码分析
public final void awaitUninterruptibly() {
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    boolean interrupted = false;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //park中被唤醒,收到中断信号也不退出,继续执行while循环
        if (Thread.interrupted())
            interrupted = true;
    }
    if (acquireQueued(node, savedState) || interrupted)
        selfInterrupt();
}

 与await()不同,awaitUninterruptibly()不会响应中断,其函数的定义中不会有中断异常抛出

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

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))
        //如果前置节点被取消或者将前置节点状态设置为SIGNAL失败
        //一般情况下不会进入这一步,所以节点不会在这里被唤醒
        LockSupport.unpark(node.thread);
    return true;
}

signal方法主要是将等待时间最长的线程,从条件等待队列移动到同步队列中,该节点需要等待前置节点唤醒。

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

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

调用unpark后,await将继续执行

public final void await() throws InterruptedException {
    ....
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //被唤醒,继续执行后续操作
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    ...
}

signalAll与signal类似,只不过signalAll会将条件等待队列中所有的节点移动到同步队列中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值