Lock Condition的那些事儿

28b47645c1fef7eb920f06b898ceb161.jpeg

Lock/Condition是Java中提供的等待通知机制,使用Condition的await和signal,类似于基于synchronized的wait和notify,二者都可以实现等待通知机制。

但是,这两者在使用和实现方式上还是有差别的。比如:

  • 等待通知机制涉及到同步队列和等待队列,Object的wait/notify只能拥有一个等待队列,而Condition可以拥有多个等待队列。

  • wait/notify的等待队列管理是由JVM控制的,而Condition的是由jdk/juc实现的。

wait/notify同步队列流程图:

c3d9ace17a9d2b2381cd0f6f9bd6ca5f.png

Condition的同步队列流程图:

28aa1a4343dc181de8879abaa8377d20.png

Lock Condition示例代码如下:

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    Thread t1 = new Thread(() -> {
        lock.lock();
        try {
            condition.await();
            System.out.println(Thread.currentThread().getName() + " await ok");
        } catch (InterruptedException ignore) {
        } finally {
            lock.unlock();
        }
    }, "t1");
    t1.start();

    Thread t2 = new Thread(() -> {
        lock.lock();
        try {
            // 将等待队列节点移动到AQS同步队列中(尾部)
            condition.signal();
            System.out.println(Thread.currentThread().getName() + " signal ok");
        } finally {
            // 唤醒AQS同步队列中阻塞的线程
            lock.unlock();
        }
    }, "t2");
    t2.start();
}

注意,await和signal必须在lock中,这是为了保证操作等待队列和同步队列的原子性,并且await有一个释放状态,也就是unlock的操作。

下面就以上述示例代码为例来分析下Condition的await和signal流程。对于线程t1来说,执行到await方法时,会添加一个节点到等待队列中,然后释放当前state,唤醒其他线程,最后阻塞自己,等待被唤醒。

public final void await() throws InterruptedException {
    // 添加节点到等待队列,该方法未考虑场景,因此需在lock中
    Node node = addConditionWaiter(); 
    // 释放当前AQS state,会唤醒同步队列中等待的线程
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // 不再同步队列中,当前线程阻塞,等待被唤醒
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 唤醒后重新获取state,获取成功后退出
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

当其他线程执行到signal时,会将之前在等待队列中的节点移动到同步队列中,最后执行unlock时唤醒同步队列中的线程。

public final void signal() {
    // 当前占用state不是自己抛异常
    if (!isHeldExclusively()) 
        throw new IllegalMonitorStateException();
    // 开始唤醒第一个节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

// 唤醒第一个处于Node.CONDITION的节点,因为有些Node可能由于超时处于其他状态,就没有必要唤醒了
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) {
    // 重置节点state,马上就要进入同步队列了
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    // 进入同步队列,如果前一个节点state>0,需要
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws>0表示前一个节点状态不对,需要设置为SIGNAL好唤醒新加入的节点node
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 如果CAS设置失败,只能先唤醒了
    return true;
}

Condition接口方法如下所示,await多了一些超时等待机制,signalAll底层和signal是一样的,这里就不在展开。

6d3f96ec05f8600cd98b292b1aff3c65.png

至此,Lock Condition相关核心代码已分析完毕,由于Condition和AQS、Object.wait/signal相关,因此关于AQS的资料可以参考AQS是如何控制线程的,关于Object.wait/signal的可以参考浅谈synchronized与Object.wait/notify原理

 推荐阅读 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值