走进AQS体系(二)—— 条件队列

概述

  • 条件队列在 并发编程中是用于对一类事务在处理时机未到的情况下,让负责处理此类事务的线程进行阻塞,当时机成熟的时候,将其唤醒,使其继续往下处理这件事务;
  • 条件队列针对阻塞于某类条件的线程进行集中化的队列管理,能保证当该条件成熟了 ,这些线程能够按照有序的方式一个个转换到同步队列中;
  • 需要区分两种唤醒,条件队列的唤醒,是每次唤醒后会将其转换到同步队列,最终这些条件队列上的CONDITION节点都是转换到同步队列中的;同步队列的唤醒,是去争夺处理机,真正去执行线程内容;所以,第一个唤醒不考虑特殊情况的话表明条件成熟了,线程就绪;第二个唤醒同样不考虑特殊情况的话表明前置节点执行完了,准备将处理机让给它,线程运行;

ConditionObject类内容说明

public class ConditionObject implements 
							Condition, java.io.Serializable {
   
    // 条件队列的第一个waiter
    private transient Node firstWaiter;
    // 条件队列的最后一个waiter
    private transient Node lastWaiter;
    /**
     * 表示重新中断退出wait模式
     */
    private static final int REINTERRUPT =  1;
    /**
     * 表示抛出 InterruptedException 退出等待模式
     */
    private static final int THROW_IE    = -1;
}

条件队列图

走进AQS体系(二)—— 条件队列_第1张图片
上方双指针链表队列 为同步队列,下方单指针链表 队列为条件队列;

条件队列进队流程描述

  1. 判断当前线程是否已经中断,已经中断的话在game over;
  2. 创建节点并从尾部进入条件队列,这里如果队列lastWaiter的waitStatus为CANCELLED,则会先对队列进行整理再进行新节点入队;
  3. await/signal 操作的前提是获取到了锁,所以await操作在节点入队之后需要对锁进行释放,释放失败的话将节点的状态设置为CANCELLED,这也是2中为什么只针对lastWaiter的状态进行判断的原因;
  4. 入队、锁释放都已完成,下一步就是需要对线程进行阻塞,阻塞的前提是节点目前不在同步队列中;阻塞的伪代码如下:
while(不在同步队列) {
   
	park(this)
	//~ 
	// 苏醒之后继续旋转判断,合适就退出
}
  1. 在第4点说了,唤醒之后会在自旋里判断是否需要继续park,但这个唤醒还不太好搞,可能是中断,也可能是signal,所以这里需要鉴别一下这两种情况。鉴别方式是,如果CAS将条件队列节点状态从CONDITION转为0,并且成功 (条件队列在成功的情况下会 将条件节点转移至同步队列) ,则说明中断发生在signal前;如果CAS失败,则说明signal发生在中断之前,因为节点 状态已经非CONDITION; 伪代码修改如下:
while(不在同步队列) {
   
	park(this)
	//~ 
	// 苏醒之后继续旋转判断,合适就退出
	if (发生中断,验证是中断发生在signal之前还是signal发生在中断之前) {
   
		break;
	}
}
  1. 节点此时已经处于同步队列,则需要走AQS独占模式的acquireQueued()方法,该方法是一个自旋,如果前节点不是头节点,则直接park,当被unparkSecussor后,继续在循环体里去尝试获取锁,成功则跳出执行,不成功则继续park;
  2. 节点传送至同步队列之后,根据该节点的nextWaiter是否为空去判断是否需要对条件队列的节点关系进行调整,对CANCELLED节点进行剔除;这里可能需要调整是因为会出现下图这种情况:
    走进AQS体系(二)—— 条件队列_第2张图片
  3. 根据acquireQueued()返回的中断bool值和步骤5判断出来的中断类型决定是否需要throw InterruptException或者interrupt()还原中断信号;如果步骤5的判断结果是中断发生在signal 前,则需要throw InterruptException;如果步骤5的判断结果是(中断发生在signal 后或者没中断),并且acquireQueued() 返回为true,则需要调用interrupt()还原中断状态;

条件队列进队流程图解

走进AQS体系(二)—— 条件队列_第3张图片

条件队列进队源码解析

1、await()

public final void await() throws InterruptedException {
   
    /**
     * 1、如果当前线程被中断, 则抛出InterruptException
     */
    if (Thread.interrupted())
        throw new InterruptedException();
    /**
     * 2、waiter 入队
     *    两件事情:1、调整条件队列关系;2、节点从尾部入队;
     */
    Node node = addConditionWaiter();
    /**
     * 3、await()是阻塞操作,并且调用await之前是需要获取到锁;
     *    所以线程入队之后需要释放已获取的锁,如果失败的话则
     *    节点入队状态为CANCELLED;
     */
    int savedState = fullyRelease(node);
    /**
     * 4、走到这里节点已经入队成功,这一步的作用是校验节点相应
     *    的条件是否已经就绪&

你可能感兴趣的:(JDK,并发,队列,多线程,java,面试)