走进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;
}

条件队列图

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

条件队列进队流程描述

  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、走到这里节点已经入队成功,这一步的作用是校验节点相应
     *    的条件是否已经就绪&
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AQS(AbstractQueuedSynchronizer)是Java中实现同步器的框架,它提供了一种基于FIFO队列的阻塞和唤醒机制。AQS的阻塞队列原理是通过CLH(Craig, Landin, and Hagersten)队列来实现的。 CLH队列是一种虚拟的双向链表,它仅存在节点之间的关联关系,而不存在队列的实例。每个请求共享资源的线程都会被封装成一个CLH队列的节点(Node)。当线程请求共享资源时,它会被添加到CLH队列的尾部,并进入阻塞状态。 当共享资源被占用时,其他线程请求该资源的线程会被放入CLH队列的末尾,即排队等待。这种排队等待的方式可以保证请求资源的线程按照FIFO的顺序获得资源,避免了饥饿现象。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。 需要注意的是,AQS的同步队列(Sync queue)是一个双向链表,包括头节点(head)和尾节点(tail),用于后续的调度。而条件队列(Condition queue)是一个单向链表,只有在使用Condition时才会存在,并且可能会有多个条件队列。 总结一下,AQS实现阻塞队列的原理是通过CLH队列来实现的,当共享资源被占用时,请求资源的线程会被添加到CLH队列中排队等待。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。同步队列用于后续的调度,而条件队列只在使用Condition时才会存在。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值