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

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() throws NullPointerException {
	        Node p = prev;
	        if (p == null)
	            throw new NullPointerException();
	        else
	            return p;
	    }
	
	    Node() {
	   	}
	
	    Node(Thread thread, Node mode) {
	        this.nextWaiter = mode;
	        this.thread = thread;
	    }
	
	    Node(Thread thread, int waitStatus) {
	        this.waitStatus = waitStatus;
	        this.thread = thread;
	    }
	}

ConditionObject成员变量

随后看一下ConditionObject的成员变量

	/** 指向等待队列中的首节点 */
    private transient Node firstWaiter;
	/** 指向等待队列中的尾节点 */
    private transient Node lastWaiter;
    /** 用于判断退出等待状态后是否要重新中断 */
    private static final int REINTERRUPT =  1;
    /** 用于判断退出等待状态后是否要抛出 InterruptedException异常 */
    private static final int THROW_IE    = -1;
    

await()等待流程

await()是最基础的Condition等待队列阻塞方法,具体代码分析见注释,其中LockSupport.park()方法的作用是阻塞当前线程,更详细的可以参考我之前的Java并发编程之LockSupport源码详解,await()源码如下:

public final void await() throws InterruptedException {
	/** 首先由第一个if判断可见,await()方法是响应中断请求的 */
    if (Thread.interrupted())
        throw new InterruptedException();
    /** 首先添加一个新的封装了当前线程的节点到等待队列 */
    Node node = addConditionWaiter();
    /** 线程释放当前资源并返回资源状态 saveState即为释放掉的资源量 */
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    /** 检查节点是否在同步队列中 signal()后会将节点重新加回同步队列 */
    while (!isOnSyncQueue(node)) {
    	/** 如果不在同步队列中则会阻塞当前线程 等待unpark()唤醒 */
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    /** 被唤醒后 调用acquireQueued方法 线程尝试获取savedState个资源 */
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    /** 清除等待状态为CANCELLED的节点 */
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    // 根据interruptMode值 来判断如何处理中断请求
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

添加新节点

addConditionWaiter()方法的作用是添加一个新的封装了当前线程的节点到等待队列,并非直接把同步队列中的节点添加到等待队列,源码如下:

	private Node addConditionWaiter() {
        Node t = lastWaiter;
        /** 此处判断如果尾节点的waitStatus(等待状态)为CANCELLED */
        if (t != null && t.waitStatus != Node.CONDITION) {
        	/** 则进入unlinkCancelledWaiters()方法清楚所有状态为CANCELLED的节点 */
            unlinkCancelledWaiters();
            /** 重新赋一个尾节点 */
            t = lastWaiter;
        }
        /** 创建一个新的Node节点 并将当前线程封装在节点内 */
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        /** 根据不同情况分别设置ConditionObject的firstWaiter、lastWaiter 和当前节点的nextWaiter  */
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }

清除等待状态为CANCELLED的节点

unlinkCancelledWaiters()用于清除等待队列中所有等待状态为CANCELLED的节点,等待状态为CANCELLED表示当前节点封装的线程放弃等待,源码如下:

	/** 
	* 代码简单易懂 本段不加注释说明
	* 唯一需要说明的是 trail变量指向 与t距离最近且等待状态为CONDITION的节点 
 	*/
	private void unlinkCancelledWaiters() {
       Node t = firstWaiter;
       Node trail = null;
       while (t != null) {
           Node next = t.nextWaiter;
           if (t.waitStatus != Node.CONDITION) {
               t.nextWaiter = null;
               if (trail == null)
                   firstWaiter = next;
               else
                   trail.nextWaiter = next;
               if (next == null)
                   lastWaiter = trail;
           }
           else
               trail = t;
           t = next;
       }
    }

调用线程释放资源

fullyRelease(Node)方法的作用是释放资源并返回最后的资源状态,release()方法在AQS文中解析过,本文不再陈列,源码如下:

	final int fullyRelease(Node node) {
		/** 用于标记资源释放是否失败 */
	    boolean failed = true;
	    try {
	    	/** 获取并在成功释放资源后返回资源状态 失败则抛出异常*/
	        int savedState = getState();
	        /** 除了获取资源外 还会唤醒同步队列中最近的一个状态非CANCELLED的节点 */
	        if (release(savedState)) {
	            failed = false;
	            return savedState;
	        } else {
	            throw new IllegalMonitorStateException();
	        }
	    } finally {
	    	/** 获取失败后同步状态被设为CANCELLED 在下次清除中移除等待队列 */
	        if (failed)
	            node.waitStatus = Node.CANCELLED;
	    }
	}

检查节点是否在同步队列中

	final boolean isOnSyncQueue(Node node) {
		/** 
		* 满足第一条件说明该节点一定在等待队列
		* 满足第二个条件说明该节点一定在同步队列
		* 均返回false
		*/
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        /** 满足条件说明该节点一定在同步队列 但node.next为null时却不一定 */
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /** 两个条件都不满足 则进入findNodeFromTail方法从同步队列尾部遍历寻找 */
        return findNodeFromTail(node);
    }

findNodeFromTail(Node)方法从同步队列的队尾向队首遍历寻找Node节点,并返回相应结果

	private boolean findNodeFromTail(Node node) {
		/** tail是同步队列的尾节点 */
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

中断请求处理

根据interruptMode判断如何处理中断请求

  1. 当interruptMode为1时,进入selfInterrupt()方法重新执行一次中断
  2. 当interruptMode为0时,什么也不做
  3. 当interruptMode为-1时,立刻相应中断抛出异常
	private void reportInterruptAfterWait(int interruptMode)
       throws InterruptedException {
        if (interruptMode == THROW_IE)
            throw new InterruptedException();
        else if (interruptMode == REINTERRUPT)
            selfInterrupt();
    }

其他版本的await

awaitUninterruptibly()

在整个await过程中不响应中断请求,但如果在执行过程中收到中断请求,会用一个标志位记录,要退出await方法时调用selfInterrupt()重新进行一次中断。

	public final void awaitUninterruptibly() {
		/** 与await()方法对比可见此处收到中断请求后不再立即抛出异常 */
	    Node node = addConditionWaiter();
	    int savedState = fullyRelease(node);
	    boolean interrupted = false;
	    while (!isOnSyncQueue(node)) {
	        LockSupport.park(this);
	        /** 遇到中断请求时用interrupted记录 */
	        if (Thread.interrupted())
	            interrupted = true;
	    }
	    /** 尝试获取资源后补上一次中断请求 */
	    if (acquireQueued(node, savedState) || interrupted)
	        selfInterrupt();
	}

awaitNanos

时限等待awaitNanos(long)、awaitUntil(Date)、await(long, TimeUnit)三个方法实现类似,此处以awaitNanos(long)为例简要分析

	public final long awaitNanos(long nanosTimeout)
        throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        Node node = addConditionWaiter();
        int savedState = fullyRelease(node);
        /** 设置限期为 当前时间+当前等待时间 */
        final long deadline = System.nanoTime() + nanosTimeout;
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
        	/** 若等待时间小于0 则在取消等待后转移回同步队列 */
            if (nanosTimeout <= 0L) {
                transferAfterCancelledWait(node);
                break;
            }
            /** 判断自旋时间阈值后调用parkNanos方法阻塞nanosTimeout时间 */
            if (nanosTimeout >= spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
            nanosTimeout = deadline - System.nanoTime();
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
        return deadline - System.nanoTime();
    }

signal()通知流程

signal()会唤醒在等待队列中等待时间最长的节点(首节点),源码如下

	public final void signal() {
		/** 检查资源持有情况 */
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }
	private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            /** 断开与等待队列的关联 */
            first.nextWaiter = null;
            /** 如果该方法内CAS操作失败 则会返回false 再次循环*/
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    final boolean transferForSignal(Node node) {
        /** CAS原子操作修改等待状态 只有节点被取消时会出现失败的情况 此处signal无竞争 */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        /** 调用enq方法将节点加入同步队列队尾 并返回node在同步队列的前驱节点*/
        Node p = enq(node);
        int ws = p.waitStatus;
        /**
        * ws>0时 等待状态一定为CANCELLED 线程不再获取资源 直接调用unpark唤醒线程
        * 否则CAS原子的将前驱节点的等待状态修改为SIGNAL 修改失败时调用unpark唤醒线程
        */
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

signalAll()的流程与signal()十分相似,不再展开讨论

总结

显示锁与内置锁等待/通知机制异同

  • Lock()显示锁提供的等待/通知机制,要比内置锁更加灵活,提供了awaitUninterruptibly()不响应中断的等待等监视器不能提供的方法。
  • Object提供的等待/通知机制只能提供一个等待队列,而基于AQS和Condition的实现可以提供多条等待队列,能满足更多场景需求。

等待通知流程

ConditionObject实现的await()方法首先将线程封装在Node节点里,然后加入等待队列we尾,同时从AQS同步队列中的移除相应节点。之后作为while循环条件调用isOnSyncQueue(Node),检查节点是否在同步队列中,如果节点不在同步队列中则会调用park()方法阻塞线程,直到被signal()中的unpark()唤醒。被唤醒后节点会被转移回同步队列,后续交由同步队列管理,线程被唤醒后会尝试获取资源,清除等待队列中等待状态为CANCELLED的节点,最后根据不同情况分别处理中断请求。


以上便是本篇文章的全部内容
作者才疏学浅,如文中出现纰漏,还望指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

7rulyL1ar

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

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

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

打赏作者

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

抵扣说明:

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

余额充值