并发之AQS原理(二) CLH队列与Node解析

并发之AQS原理(二) CLH队列与Node解析

1.CLH队列与Node节点

就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队。

一条队列是队列中每一个人的组织形式。那么每个人决定怎么看待自己在队列中的形态决定了整个队列的形态。比如当每个人都遵守先来后到的原则时,那么最先来的人会站到第一个,之后每个人都会顺序排开。

同样的队列这个类不存在,让他们形成队列的是每个节点类的组织形式。所以想分析队列就必须要先分析节点。

所谓的CLH队列本质上就是一个双向链表Node就是该链表的节点。当然CLH队列并不是简单的双向链表

1195582-20190531083244552-1167398071.png

上图直观的向我们展示了节点的组织状态,我们可以看看node节点的源代码。

2.node节点属性的解析

node节点作为CLH队列的一个节点,有着5条属性,分别是wateStatus 、prev、next、thread、nextWater。下面我们将一一解析这五种属性的作用。

wateStatus介绍

wateStatus是当前节点的一个等待状态标志位,该标志位决定了该节点在当前情况下处于何种状态。

不用再说了,直接看注释吧。这里我们说下Node。Node结点是对每一个访问同步代码的线程的封装,其包含了需要同步的线程本身以及线程的状态,如是否被阻塞,是否等待唤醒,是否已经被取消等。变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。

CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。

SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。

CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。

AQS运用该属性时的状态判断

状态判断结果说明
waitStatus=0代表初始化状态该节点尚未被初始化完成
waitStatus>0取消状态说明该线程中断或者等待超时,需要移除该线程
waitStatus<0有效状态该线程处于可以被唤醒的状态
prve next thread介绍

prve 是同步线程队列中保存的前置节点的地址。
next 是同步线程队列中保存的后续节点的地址。
thread 同步线程队列主要存储的线程信息。

nextWaiter介绍

AQS中阻塞队列采用的是用双向链表保存,用prve和next相互链接。而AQS中条件队列是使用单向列表保存的,用
nextWaiter来连接。阻塞队列和条件队列并不是使用的相同的数据结构。

在Node节点的源码中有两个常量属性

// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 其他模式
// 其他非空值:条件等待节点(调用Condition的await方法的时候)

nextWaiter实际上标记的就是在该节点唤醒后依据该节点的状态判断是否依据条件唤醒下一个节点。

nextWaiter状态标志说明
SHARED(共享模式)直接唤醒下一个节点
EXCLUSIVE(独占模式)等待当前线程执行完成后再唤醒
其他非空值依据条件决定怎么唤醒下一个线程。类似semaphore中控制几个线程通过

node节点的属性介绍完了,下面来介绍node节点的方法以及各个方法的用户

3.node节点方法解析

构造方法
// 构造方法为空参构造,一般用于创建head节点,或者为nextWaiter设置共享标志。
Node() {
}
// 构造方法用于创建一个带有条件队列的节点
Node(Thread thread, Node mode) {
    this.nextWaiter = mode;
    this.thread = thread;
}
// 用于创建一个带有初始等waitStatus的节点
Node(Thread thread, int waitStatus) {
    this.waitStatus = waitStatus;
    this.thread = thread;
}
isShared方法

显而易见这个方法使用来检查当前节点是否为共享节点。


      final boolean isShared() {
          return nextWaiter == SHARED;
      }
predecessor方法

该方法用来查找前置节点是否存在,相当于为前置节点查空。

final Node predecessor() throws NullPointerException {
  Node p = prev;
  if (p == null)
    throw new NullPointerException();
  else
    return p;
}

4.基于Node的的CLH阻塞队列是如何运作的

首先 CLH队列锁通常使用自旋锁来阻塞线程执行,使用本节点和前置节点的waitStatus来判断线程是否阻塞。在前置节点获取执行权限的时候发出信号。每个节点都有一个单独等待通知的监视器,waitStatus不会控制线程是否获取到了锁。获取锁的过程是通过查看队列中的第一个node中的waitStatus是否处于可以执行的状态。如果可执行则继续执行,线程被中断或者超时了就寻找后续node.

CLH锁出列只设置更新头部节点,插入队列只需要原子更新尾部的节点。

首先确定自己是否为头部节点,如果是头部节点则直接获取资源开始执行,如果不是则自旋前置节点直到前置节点执行完成状态修改为CANCELLED,然后断开前置节点的链接,获取资源开始执行。

1195582-20190531083229686-1272222252.png

这部分操作的具体详情会在后续的系列中详细讲解。

5.总结

CLH阻塞队列采用的是双向链表队列,头部节点默认获取资源获得执行权限。后续节点不断自旋方式查询前置节点是否执行完成,直到头部节点执行完成将自己的waitStatus状态修改以通知后续节点可以获取资源执行。CLH锁是一个有序的无饥饿的公平锁。

转载于:https://www.cnblogs.com/yanlong300/p/10953185.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值