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

本文详细探讨了AQS(AbstractQueuedSynchronizer)中的条件队列,包括其在并发编程中的作用、ConditionObject类的结构、条件队列的进队和出队流程,以及相关源码解析。通过分析,阐述了线程如何在条件成熟时从条件队列转移到同步队列,并在不同情况下处理中断和唤醒操作。
摘要由CSDN通过智能技术生成

概述

  • 条件队列在 并发编程中是用于对一类事务在处理时机未到的情况下,让负责处理此类事务的线程进行阻塞,当时机成熟的时候,将其唤醒,使其继续往下处理这件事务;
  • 条件队列针对阻塞于某类条件的线程进行集中化的队列管理,能保证当该条件成熟了 ,这些线程能够按照有序的方式一个个转换到同步队列中;
  • 需要区分两种唤醒,条件队列的唤醒,是每次唤醒后会将其转换到同步队列,最终这些条件队列上的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;
}

条件队列图

条件队列和同步队列
上方双指针链表队列 为同步队列,下方单指针链表 队列为条件队列;

条件队列进队流程描述

  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节点进行剔除;这里可能需要调整是因为会出现下图这种情况:
    进队冲突
  3. 根据acquireQueued()返回的中断bool值和步骤5判断出来的中断类型决定是否需要throw InterruptException或者interrupt()还原中断信号;如果步骤5的判断结果是中断发生在signal 前,则需要throw InterruptException;如果步骤5的判断结果是(中断发生在signal 后或者没中断),并且acquireQueued() 返回为true,则需要调用interrupt()还原中断状态;

条件队列进队流程图解

条件队列入队出队流程图

条件队列进队源码解析

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、走到这里节点已经入队成功,这一步的作用是校验节点相应
     *    的条件是否已经就绪,如果已经就绪(signal)或者被中断
     *    interrupt的话条件节点会被移动到同步队列;
     */
    int interruptMode = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值