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

1.CLH队列与Node节点

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

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

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

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

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

2.node节点属性的解析

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

waitStatus介绍

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

不用再说了,直接看注释吧。这里我们说下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,然后断开前置节点的链接,获取资源开始执行。

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

5.总结

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

并发之AQS原理(二) CLH队列与Node解析的更多相关文章

  1. 并发之AQS原理(三) 如何保证并发

    并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...

  2. Java并发之AQS原理解读(二)

    上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...

  3. 并发之AQS原理(一) 原理介绍简单使用

    并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...

  4. Java并发之AQS原理解读(三)

    上一篇:Java并发之AQS原理解读(二) 前言 本文从源码角度分析AQS共享锁工作原理,并介绍下使用共享锁的子类如何工作的. 共享锁工作原理 共享锁与独占锁的不同之处在于,获取锁和释放锁成功后,都会 ...

  5. Java并发之AQS原理解读(一)

    前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...

  6. AQS(一) 对CLH队列的增强

    基本概念 AQS(AbstractQueuedSynchronizer),顾名思义,是一个抽象的队列同步器. 它的队列是先进先出(FIFO)的等待队列 基于这个队列,AQS提供了一个实现阻塞锁的机制 ...

  7. 从ReentrantLock实现非公平锁的源码理解AQS中的CLH队列

    虽然前面也看过AQS的文章,并且转载过一篇大佬的分析,但是我觉得他们对于AQS和ReentrantLock部分的源码的分析并不详细,自己理解期来还是有问题,于是自己准备花时间重新梳理下,好了,进入正题 ...

  8. Java并发之AQS原理剖析

    概述: AbstractQueuedSynchronizer,可以称为抽象队列同步器. AQS有独占模式和共享模式两种: 独占模式: 公平锁: 非公平锁: 共享模式: 数据结构: 基本属性: /** ...

  9. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

随机推荐

  1. 如何开始使用 Akka

    如果你是第一次开始使用 Akka,我们推荐你先运行简单的 Hello World 项目.情况参考  Quickstart Guide 页面中的内容来下载和运行 Hello World 示例程序.上面链 ...

  2. django + jquery 实现二级联动

    二级联动用ajax还是很好实现的,下面简单给个例子 jquery代码 $("#id_sel").change(function(){ $.get("/browser/ge ...

  3. unittest详解(六) 断言

    我们在执行测试用例时,怎么来判断这条用例是否通过呢?唯一的办法就是拿实际结果和预期结果进行比较,如果一致用例就是通过的,否则用例就是失败的.在python中这种比较的方法就叫做断言,unittest框 ...

  4. 分治NTT:我 卷 我 自 己

    感觉这种东西每次重推一遍怪麻烦的,就写在这里了. 说白了就是根据分治区间左端点是否为\(0\)分类讨论一下,一般是如果不是\(0\)就要乘\(2\),不过还是需要具体问题具体分析一下才好(就比如下面的 ...

  5. JavaScript实现页面滚动到div区域div以动画方式出现

    用JavaScript实现页面滚动效果,以及用wow.js二种方式实现网页滚动效果 要实现效果是页面滚动到一块区域,该区域以动画方式出现. 这个效果需要二点: 一:我们要先写好一个css动画. 二:用 ...

  6. JavaWeb_JSTL标签数据的存储

    菜鸟教程 传送门 JSTL jar包下载 JSTL[百度百科]:(JavaServer Pages Standard Tag Library,JSP标准标签库)是一个不断完善的开放源代码的JSP标签库 ...

  7. [JZO6401]:Time(贪心+树状数组)

    题目描述 小$A$现在有一个长度为$n$的序列$\{x_i\}$,但是小$A$认为这个序列不够优美. 小$A$认为一个序列是优美的,当且仅当存在$k\in [1,n]$,满足:$$x_1\leqsla ...

  8. 通过Flink实现个推海量消息数据的实时统计

    背景 消息报表主要用于统计消息任务的下发情况.比如,单条推送消息下发APP用户总量有多少,成功推送到手机的数量有多少,又有多少APP用户点击了弹窗通知并打开APP等.通过消息报表,我们可以很直观地看到 ...

  9. Spring boot之JPA/Hibernate/Spring Data

    1.什么是JPA? JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. JPA(Java Per ...

  10. mysql 查询一个月的数据

    //今天 select * from 表名 where to_days(时间字段名) = to_days(now()); //昨天 SELECT * FROM 表名 WHERE TO_DAYS( NO ...