前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lock Condition的那些事儿

Lock Condition的那些事儿

作者头像
luoxn28
发布2021-04-26 12:16:43
4400
发布2021-04-26 12:16:43
举报
文章被收录于专栏:TopCoderTopCoder

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

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

  • 等待通知机制涉及到同步队列和等待队列,Object的wait/notify只能拥有一个等待队列,而Condition可以拥有多个等待队列。
  • wait/notify的等待队列管理是由JVM控制的,而Condition的是由jdk/juc实现的。

wait/notify同步队列流程图:

Condition的同步队列流程图:

Lock Condition示例代码如下:

代码语言:javascript
复制
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,唤醒其他线程,最后阻塞自己,等待被唤醒。

代码语言:javascript
复制
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时唤醒同步队列中的线程。

代码语言:javascript
复制
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是一样的,这里就不在展开。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TopCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档