Condition 分析与总结

在没有 Lock 之前,我们使用 synchronized 来控制同步,配合 Object 的 #wait()#notify() 等一系列方法可以实现等待 / 通知模式

而Object 的 #wait()#notify()的缺点主要在于一个锁只支持一个等待队列,如果一个锁上的等待条件又多个,一个条件满足后,会唤醒等待队列中所有的线程,而等待队列中有些线程是在等待其他的条件,也就是过早唤醒问题”这就造成了一些线程被不必要的唤醒增加线程切换次数影响性能。

所以引入了Condition,它能够在一个锁上创建多个条件队列,解决以上问题。

代码如下:

public class ConditionObject implements Condition, java.io.Serializable {
    /** First node of condition queue. */
    private transient Node firstWaiter; // 头节点
    /** Last node of condition queue. */
    private transient Node lastWaiter; // 尾节点
    
    public ConditionObject() {
    }

    // ... 省略内部代码
}

从上面代码可以看出,ConditionObject 拥有首节点(firstWaiter),尾节点(lastWaiter)。当前线程调用 #await()方法时,将会以当前线程构造成一个节点(Node),并将节点加入到该队列的尾部。结构如下:

Condition 等待队列

  • Node 里面包含了当前线程的引用。Node 定义与 AQS 的 CLH 同步队列的节点使用的都是同一个类(AbstractQueuedSynchronized 的 Node 静态内部类)。

  • ConditionObject 的队列结构比 CLH 同步队列的结构简单些,是一个FIFO单向链表。

 

以下是AQS队列和Condition队列的出入结点的示意图,转自 https://blog.csdn.net/coslay/article/details/45217069

可以通过这几张图看出线程结点在两个队列中的出入关系和条件。

I.初始化状态:AQS等待队列有3个Node,Condition队列有1个Node(也有可能1个都没有)

II.节点1执行Condition.await() 
1.将head后移 
2.释放节点1的锁并从AQS等待队列中移除 
3.将节点1加入到Condition的等待队列中 
4.更新lastWaiter为节点1

III.节点2执行signal()操作 
5.将firstWaiter后移 
6.将节点4移出Condition队列 
7.将节点4加入到AQS的等待队列中去 
8.更新AQS的等待队列的tail

总结:

一个线程获取锁后,通过调用 Condition 的 #await() 方法,会将当前线程先加入到条件队列中,然后释放锁,最后通过 #isOnSyncQueue(Node node) 方法,不断自检看节点是否已经在 CLH 同步队列了,如果是则尝试获取锁,否则一直挂起。

当线程调用 #signal() 方法后,程序首先检查当前线程是否获取了锁,然后通过#doSignal(Node first) 方法唤醒CLH同步队列的首节点。被唤醒的线程,将从 #await() 方法中的 while 循环中退出来,然后调用 #acquireQueued(Node node, int arg) 方法竞争同步状态。

源码分析

http://www.iocoder.cn/JUC/sike/Condition/

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值