并发之AQS原理(二) CLH队列与Node解析
并发之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解析的更多相关文章
- 并发之AQS原理(三) 如何保证并发
并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...
- Java并发之AQS原理解读(二)
上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...
- 并发之AQS原理(一) 原理介绍简单使用
并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...
- Java并发之AQS原理解读(三)
上一篇:Java并发之AQS原理解读(二) 前言 本文从源码角度分析AQS共享锁工作原理,并介绍下使用共享锁的子类如何工作的. 共享锁工作原理 共享锁与独占锁的不同之处在于,获取锁和释放锁成功后,都会 ...
- Java并发之AQS原理解读(一)
前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...
- AQS(一) 对CLH队列的增强
基本概念 AQS(AbstractQueuedSynchronizer),顾名思义,是一个抽象的队列同步器. 它的队列是先进先出(FIFO)的等待队列 基于这个队列,AQS提供了一个实现阻塞锁的机制 ...
- 从ReentrantLock实现非公平锁的源码理解AQS中的CLH队列
虽然前面也看过AQS的文章,并且转载过一篇大佬的分析,但是我觉得他们对于AQS和ReentrantLock部分的源码的分析并不详细,自己理解期来还是有问题,于是自己准备花时间重新梳理下,好了,进入正题 ...
- Java并发之AQS原理剖析
概述: AbstractQueuedSynchronizer,可以称为抽象队列同步器. AQS有独占模式和共享模式两种: 独占模式: 公平锁: 非公平锁: 共享模式: 数据结构: 基本属性: /** ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
随机推荐
- ELEMENT-UI 封装el-table 局部刷新row
//关于封装的el-table行数据更新后如何局部更新row row.status=status; this.$set(this.$refs.elTable.$data.tableData,index ...
- 二叉排序树(Binary Sort Tree)
1.定义 二叉排序树(Binary Sort Tree)又称二叉查找(搜索)树(Binary Search Tree).其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树: ① 若它的左子树 ...
- TTTTTTTTTTTTTTTTTT POJ 2724 奶酪消毒机 二分匹配 建图 比较难想
Purifying Machine Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 5004 Accepted: 1444 ...
- PTA 刷题与Z老师的头发
刷题与Z老师的头发 (10 分) 在Pintia上,每天Z老师出题.小盆友们刷题.Z老师的头发遵从以下规律: 1.每天生长出60根头发: 2.每出一道题,减少20根头发: 3.每天结束时统计累积做题情 ...
- ubuntu搭建、安装gitlab服务器以及初始化密码
本为14.04 在搭建之前要确定其网络环境是没有问题.用root身份进行操作 1.安装和配置必要的依赖关系 apt-get update apt-get install -y curl openssh ...
- hdu 4763 看毛片(单纯next数组的应用--纯正O(n))
因为需要负责队内的字符串题,开始刷,做到这道,开始想不出来,上网找题解, 然后就惊了,为什么你们这么暴力都可以过的啊,1e6啊,后来又想了下会做了 贴下代码 #include <iostream ...
- HBase2.0新特性解析
作者 | 个推大数据运维工程师 行者 升级背景 个推作为专业的数据智能服务商,在业务开展过程中存在海量的数据存储与查询的需求,为此个推选用了高可靠.高性能.面向列.可伸缩的分布式数据存储系统--HBa ...
- Mysql超强卸载
1.控制面板——>所有控制面板项——>程序和功能,卸载mysql server! 2.删除MySQL文件,尤其是ProgramData里面的隐藏文件MySQL,我当时没有删除,重新安装My ...
- 使用relo
安装好插件以后,选择一个工程,右键点击relo connection(如果已经连接了就不需要点击).点击navigate,open relo session,选择一个文件,可以打开这个文件的类图,点击 ...
- AS基本设置
1,点开as之前应该做的事 很多人一定会在这个界面卡顿很久,其实这是as在检测更新.所以我们在点开as之前先修改它的配置文件,让它不再更新. 找到你安装as的目录,进入bin文件夹找到idea.pr ...