Java并发编程之Condition接口与ConditionObject等待队列源码详解

本文详细介绍了Condition接口如何配合Lock接口实现等待/通知模式,支持多等待队列,以及为何在复杂场景中不可或缺。通过对比Object的监视器方法,阐述了Condition在并发编程中的优势和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Condition接口

Condition接口能做什么

Condition接口提供了类似java.lang.Object所提供的监视器方法,配合Lock接口可以实现等待/通知模式。首先援引一张《Java并发编程的艺术》之中的表格来展示二者的特点与异同。

对比项 Object监视器方法 Condition
前置条件 获取对象的锁 调用Lock.lock()获取锁
调用Lock.newCondition()获取Condition对象
调用方式 直接调用
如:object.wait()
直接调用
如:condition.await()
等待队列个数 一个 多个
当前线程释放锁进入等待状态 支持 支持
当前线程释放锁并进入等待状态
且在等待状态中不响应中断
不支持 支持
当前线程释放锁并进入超时等待状态 支持 支持
当前线程释放锁并进入等待状态到将来的某个时间 不支持 支持
唤醒等待队列中的一个线程 支持 支持
唤醒等待队列中的全部线程 支持 支持

为什么需要Condition

在前文的表格中其实已经可以窥见这个问题的端倪,除了Condition所支持的等待/通知模式增加了更多等待状态的支持,最关键的在于等待队列的个数,即Object的一组监视器方法(搭配内置锁)只能支持一个等待队列,每个Condition对象都包含一个等待队列,可以使用多个Conditon(搭配Lock及同步器AQS)以实现多等待队列(每个队列对应不同等待条件)。

设想这样一种情况,若干线程在等待队列上可以有多个唤醒条件,假设出现事件A和事件B时分别唤醒唤醒某组线程,在这种情况下,如果使用内置锁搭配Object的监视器方法,显然不能满足需求,因为notify和notifyAll都不能唤醒指定线程,此时便需要使用显示锁Lock搭配Condition以实现多等待队列满足需求。

Condition接口定义

public interface Condition {
   
	/** 基础阻塞方法 */
	void await() throws InterruptedException;
	/** 阻塞且不响应中断 */
	void awaitUninterruptibly();
	/** 等待nanosTimeout时间后 唤醒线程 */
	long awaitNanos(long nanosTimeout) throws InterruptedException;
	/** 等效于awaitNanos(unit.toNanos(time) */
	boolean await(long time, TimeUnit unit) throws InterruptedException;
	/** 时间超过deadline时 唤醒线程 */
	boolean awaitUntil(Date deadline) throws InterruptedException;
	/** 唤醒一个等待队列上的线程 */
	void signal();
	/** 唤醒全部等待队列上的线程 */
	void signalAll();
}

ConditionObject等待队列实现/设计思路

ConditionObject是Condition的具体实现类,由ConditionObject实现了等待队列,等待队列是一个FIFO队列,等待队列中的每个Node节点都保存线程及相关信息,采取链式存储。
等待队列结构示意图如下:
在这里插入图片描述

同步队列与等待队列

当有线程尝试获取资源时,线程会被封装在Node节点中并加入同步队列,同步队列的首个节点是成功获取资源的节点,其余节点均进入阻塞状态,等待尝试获取资源。同步队列中阻塞的线程都是要等待尝试获取资源的。

当同步队列中的线程调用了ConditionObject提供的等待方法后,线程会释放当前资源,并将封装了当前线程的节点加入等待队列。在等待队列中的线程均为阻塞状态且不会尝试获取资源,等待其他线程通知后重新加入同步队列尝试获取资源。

某同步器内同步队列与等待队列结构示意图如下:
在这里插入图片描述

等待

调用ConditionObject提供的await()方法,同步队列的首节点会首先释放资源,然后唤醒同步队列中的后继节点,随后将当前线程加入到等待队列中并阻塞,此过程在下文源码分析中会详细介绍。
此过程示意图如下:
在这里插入图片描述

通知

调用ConditionObject提供的signal()方法,会唤醒在等待队列中等待时间最长的节点,即首节点,并将对应线程重新添加到同步队列中,并尝试获取资源(如果获取资源仍会被阻塞)。
此过程示意图如下:
在这里插入图片描述

ConditionObject源码解析

ConditionObject是同步器AbstractQueuedSynchronizer(以下简称AQS)的内部类,同步器AQS是用于实现阻塞锁和同步组件的基础框架,AQS详解见我之前的文章Java并发编程之队列同步器AQS源码详解,接下来一起通过ConditionObject以及AQS内相关实现方法源码分析等待队列是如何实现的。

注1:为了更好的理解Condition,建议先读AQS相关的文章,因为AQS中维护的同步队列和Condition所实现的等待队列都是基于Node节点实现的,先前文章已经分析过,本篇不再赘述,且二者在实际应用中搭配使用,Condition等待队列的分析也离不开AQS的同步队列。

注2:本文陈列并分析的不是全部源码,部分无关部分省略。

注3:由Condition实现的队列可称作条件队列,本文后文都将其称为等待队列,由AQS实现维护的称作同步队列。

Node节点

首先看一下Node节点的定义,源码如下:

	static final class Node {
   
		/** 表示当前是共享模式 */
		 static final Node SHARED = new Node();
		/** 表示当前是独占模式 */
	    static final Node EXCLUSIVE = null;
		/** 表示当前节点线程取消等待 */
	    static final int CANCELLED =  1;
		/** 表示当前节点释放资源或被需要 */
	    static final int SIGNAL    = -1;
		/** 表示该节点在Condition等待队列上等待 唤醒后重新加入同步队列 */
	    static final int CONDITION = -2;
		/** 仅用于共享模式 表示下次同步状态的获取会向后传播 */
	    static final int PROPAGATE = -3;
		/** 等待状态字段 为0时表示初始状态 其余值含义分别对应上面四个常量的注释 */
	    volatile int waitStatus;
		/** 同步队列中所使用的前驱节点 */
	    volatile Node prev;
		/** 同步队列中所使用的后继节点 */
	    volatile Node next;
		/** 等待获取资源的线程 */
	    volatile Thread thread;
		/** 指向下一个节点 */
	    Node nextWaiter;
	
	    final boolean isShared() {
   
	        return nextWaiter == SHARED;
	    }
		/** 检查前驱节点 */
	    final Node predecessor
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值