AQS中的condition源码原理详细分析

举报
breakDawn 发表于 2022/10/23 23:01:32 2022/10/23
【摘要】 condition的用法 condition 和 object.wait/notify的区别 condition原理分析 超大原理流程图 代码结构部分: 原理实现部分 等待队列 等待过程 唤醒过程signal() condition的用法condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。和直接用lock\unlock去做等待通知的区别在于,lock是不会释放...

condition的用法

condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。

和直接用lock\unlock去做等待通知的区别在于,lock是不会释放锁的,但是利用的condition的await则可以,且唤醒后会自动重新拿回锁。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        	// if(xxxx)判断不满足条件,等待,释放锁
            condition.await();
    } finally {
            lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        	// 做完事情了,通知condition上等待的开始抢占
            condition.signal();
    } finally {
            lock.unlock();
    }
}

也提供了一些支持中断、支持超时的等待方法

condition 和 object.wait/notify的区别

  1. object的wait依赖sync, 只能最多有一个等待队列。 而通过newCondition可以制造多个等待队列

  2. wait不支持中断,而condition支持

  3. condition支持等待特定时间

condition原理分析

超大原理流程图

  • await(), 简单来讲就是把当前线程放入condition的等待队列中,然后调用LockSupport.park拉起线程。如果被其他线程通过signal唤醒,则放入同步队列中竞争锁,竞争成功则返回,否则继续竞争。

  • signal方法,就是拿到condition的等待队列头节点,用cas修改节点状态,改成功则唤醒线程。但有可能被别人抢先,所以需要cas操作。
    image.png

代码结构部分:

​ Lock提供了newCondition接口给外部锁调用

​ 而newCondition()返回的Condition是一个接口
image.png

​ 这个接口的实现类是ConditionObject,放在AQS抽象类的内部类中

image.png

原理实现部分

等待队列

  • 每个condition都有一个属于自己的等待队列

  • 每次调用condition.await, 就插入到等待队列尾部

  • 等待队列插入封装线程的节点时不需要在尾部CAS, 因为必须先获取锁,才能调用await,因此不用CAS竞争

  • 每个Lock只有一个同步队列(用于lock()时阻塞和竞争用), 但是可能会有多个等待队列(用于condition的await)

等待过程

  1. 添加线程到condition的等待队列尾部

  2. 释放占用的锁,并唤醒同步队列的后继节点

  3. 此时肯定不在aqs的同步队列中了, 用park方法进入阻塞状态

  4. 被唤醒,唤醒时可能是通过sign()被人放入了同步队列, 也可能是被中断唤醒,因此要做checkInterruptWhileWaiting检查看是否继续, 如果同意继续,就继续睡眠,直到进入同步队列

  5. 尝试acquireQueued竞争和抢占state同步状态

  6. 退出前,顺带用unlinkCancelledWaiters清理已经不是CONDITION状态的等待队列节点

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 添加本线程到等待队列尾部
    Node node = addConditionWaiter();
    // 释放锁,唤醒同步队列中的后继节点
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 如果已经在同步队列中了,说明被成功sign唤醒
    while (!isOnSyncQueue(node)) {
        // 阻塞挂起
        LockSupport.park(this);
        // 确认是否需要中断时就退出
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 清理已经不是CONDITION状态的等待队列节点
    if (node.nextWaiter != null) 
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

唤醒过程signal()

  1. 检查调用signal时,是否当前线程获取了锁,不是则抛异常

    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
  2. 获取condition队列中的第一个等待节点

    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
    
  3. 用CAS清除CONDITION状态

    if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
        return false;
    
  4. 调用AQS的enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要CAS支撑?因为可能是共享的,即使获取了锁也需要竞争)

    Node p = enq(node);
    
  5. 移动入同步队列成功后(可能经历了几次CAS),再用unpark方法唤醒,那个线程就进入了上面代码中Park之后的部分了

    int ws = p.waitStatus;
    if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    
  6. 如果是signalAll方法,则等待队列中每个节点都执行一次signal方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过sign唤醒了)

do {
    Node next = first.nextWaiter;
    first.nextWaiter = null;
    transferForSignal(first);
    first = next;
} while (first != null);
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。