原文链接:https://blog.csdn.net/chenssy/article/details/60781148

AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。

CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其定义如下:

static final class Node {
/** 共享 */
static final Node SHARED = new Node(); /** 独占 */
static final Node EXCLUSIVE = null; /**
* 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
*/
static final int CANCELLED = 1; /**
* 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
*/
static final int SIGNAL = -1; /**
* 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,改节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
*/
static final int CONDITION = -2; /**
* 表示下一次共享式同步状态获取将会无条件地传播下去
*/
static final int PROPAGATE = -3; /** 等待状态 */
volatile int waitStatus; /** 前驱节点 */
volatile Node prev; /** 后继节点 */
volatile Node next; /** 获取同步状态的线程 */
volatile Thread thread; Node nextWaiter; final boolean isShared() {
return nextWaiter == SHARED;
} final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
} Node() {
} Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
} Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}

CLH同步队列结构图如下:

入列

学了数据结构的我们,CLH队列入列是再简单不过了,无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。代码我们可以看看addWaiter(Node node)方法:

private Node addWaiter(Node mode) {
//新建Node
Node node = new Node(Thread.currentThread(), mode);
//快速尝试添加尾节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS设置尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//多次尝试
enq(node);
return node;
}

addWaiter(Node node)先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点

  private Node enq(final Node node) {
//多次尝试,直到成功为止
for (;;) {
Node t = tail;
//tail不存在,设置为首节点
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//设置为尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}

在上面代码中,两个方法都是通过一个CAS方法compareAndSetTail(Node expect, Node update)来设置尾节点,该方法可以确保节点是线程安全添加的。在enq(Node node)方法中,AQS通过“死循环”的方式来保证节点可以正确添加,只有成功添加后,当前线程才会从该方法返回,否则会一直执行下去。

过程图如下: 

出列

CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。过程图如下:

CLH同步队列的更多相关文章

  1. 【死磕Java并发】-----J.U.C之AQS:CLH同步队列

    此篇博客全部源代码均来自JDK 1.8 在上篇博客[死磕Java并发]-–J.U.C之AQS:AQS简单介绍中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...

  2. J.U.C之AQS:CLH同步队列

    此篇博客所有源码均来自JDK 1.8 在上篇博客[死磕Java并发]—–J.U.C之AQS:AQS简介中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个FIFO ...

  3. Java并发包源码学习系列:CLH同步队列及同步资源获取与释放

    目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...

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

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

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

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

  6. 学习JUC源码(1)——AQS同步队列(源码分析结合图文理解)

    前言 最近结合书籍<Java并发编程艺术>一直在看AQS的源码,发现AQS核心就是:利用内置的FIFO双向队列结构来实现线程排队获取int变量的同步状态,以此奠定了很多并发包中大部分实现基 ...

  7. JAVA多线程经典问题 -- 生产者 消费者 同步队列实现方法

    在JAVASE5 中的java.util.concurrent.BlockingQueue支持,BlockingQueue是一个接口但是我们通常可以使用LinkedBlockingQueue,它是一个 ...

  8. Java并发包之同步队列SynchronousQueue理解

    1 简介 SynchronousQueue是这样一种阻塞队列,其中每个put必须等待一个take,反之亦然.同步队列没有任何内部容量,甚至连一个队列的容量都没有.不能在同步队列上进行peek,因为仅在 ...

  9. 【转载】阻塞队列之三:SynchronousQueue同步队列 阻塞算法的3种实现

    一.SynchronousQueue简介 Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除 ...

随机推荐

  1. PostgreSQL 9.5 客户端认证

    PostgreSQL 9.5 客户端认证 当一个客户端应用连接一个数据库服务器时,它将指定以哪个PostgreSQL 数据库用户名连接,就像我们以一个特定用户登录一台 Unix 计算机一样.在 SQL ...

  2. PHP类(二)-类的构造方法和析构方法

    构造方法 构造方法是对象创建完成后第一个被对象自动调用的方法,用来完成对象的初始化 在每个类中都会有一个构造方法,如果没有声明的话,类中会存在一个没有参数列表并且内容为空的构造方法.如果声明的话,默认 ...

  3. 第三章 深入分析Java Web的中文乱码问题(待续)

    几种常见的编码格式 在Java中需要编码的场景 在Java中如何编解码 在Java Web中涉及的编解码 在JS中的编码问题 常见问题分析 一种繁简转换的实现方式

  4. 01-19asp.net网站--关于“应用程序中的服务器错误(需添加"Jquery"ScriptRescourseMapping)”

    一般打开网页进行加载时(有缓存),会弹出以下对话框. 但是如果网页加载后出现以下错误,就是应用程序的问题了.如果出现这种问题,就需要在安装Csharp的根目录下,找到一个名为.dll结尾的Jquery ...

  5. TCS3200颜色传感器测试实验

    TCS3200颜色传感器测试实验 2013-08-02 17:18:24 分享: 标签:  Arduino  TCS3200  传感器 TCS3200颜色传感器是一款全彩的颜色检测器,包括了一块TAO ...

  6. mysql中表触发器的简单使用

    以前有接触过触发器,但没有亲自写过,所以在这里简单写个例子: 功能为:两张表t_test,t_emp;给t_test添加触发器让t_test每插入一条数据,就给t_emp中num字段值增1: 1,建立 ...

  7. [patl2-014]列车调度

    解题关键:由Dilworth定理(最小反链划分 == 最长链)可知最少的下降序列个数就等于整个序列最长上升子序列的长度,此题即转化为求最长上升子序列的长度. #include<cstdio> ...

  8. Codeforces 1077E (二分乱搞或者dp)

    题意:给你一个数组,可以从中选区若干种元素,但每种元素选区的个数前一种必须是后一种的2倍,选区的任意2种元素不能相同,问可以选取最多的元素个数是多少? 思路1(乱搞):记录一下每种元素的个数,然后暴力 ...

  9. 760. Find Anagram Mappings乱序字符串的坐标位置

    [抄题]: Given two lists Aand B, and B is an anagram of A. B is an anagram of A means B is made by rand ...

  10. 前端学习笔记2017.6.21-html和浏览器的关系以及开发工具

    html文档是一种文件格式, 浏览器可以识别这种文件格式,并能把html文档里面的内容解析出来. 用更贴近程序员的话说就是:html相当于c语言,浏览器相当于c编译器. 开发工具用sublime te ...