此篇博客全部源代码均来自JDK 1.8

在上篇博客【死磕Java并发】—–J.U.C之AQS:AQS简单介绍中提到了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),而后继节点将会在获取同步状态成功时将自己设置为首节点。这个过程很easy,head运行该节点并断开原首节点的next和当前节点的prev就可以,注意在这个过程是不须要使用CAS来保证的,由于仅仅有一个线程能够成功获取到同步状态。过程图例如以下:

參考资料

Doug Lea:《Java并发编程实战》

方腾飞:《Java并发编程的艺术》


欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!

–— Java成神之路: 488391811(一起走向Java成神) –—

【死磕Java并发】-----J.U.C之AQS:CLH同步队列的更多相关文章

  1. 【死磕Java并发】-----Java内存模型之happend-before

    在上篇博客([死磕Java并发]-–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的情况下 ...

  2. 【死磕Java并发】----- 死磕 Java 并发精品合集

    [死磕 Java 并发]系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览. 先来一个总览图: [高清图,请关注"Java技术驿站&quo ...

  3. 【死磕Java并发】-----Java内存模型之happens-before

    在上篇博客([死磕Java并发]-–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的情况下 ...

  4. 【死磕Java并发】-----内存模型之happens-before

    在上篇博客([死磕Java并发]-----深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的 ...

  5. Java并发-J.U.C之AQS

    AQS(Abstract Queue Synchronizer)介绍 [死磕Java并发]—–J.U.C之AQS(一篇就够了) 下面讲解具体的Java并发工具类 1 CountDownLatch 参考 ...

  6. 【死磕Java并发】—–J.U.C之AQS(一篇就够了)

    [隐藏目录] 1 独占式 1.1 独占式同步状态获取 1.2 独占式获取响应中断 1.3 独占式超时获取 1.4 独占式同步状态释放 2 共享式 2.1 共享式同步状态获取 2.2 共享式同步状态释放 ...

  7. 【死磕Java并发】-----深入分析volatile的实现原理

      通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它 ...

  8. 【死磕Java并发】—–深入分析volatile的实现原理

    通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它比使 ...

  9. 【死磕Java并发】-----深入分析synchronized的实现原理

    记得刚刚開始学习Java的时候.一遇到多线程情况就是synchronized.相对于当时的我们来说synchronized是这么的奇妙而又强大,那个时候我们赋予它一个名字"同步". ...

随机推荐

  1. 对于矩阵的理解-- by 孟岩老师

    “如果不熟悉线性代数的概念,要去学习自然科学,现在看来就和文盲差不多.” --瑞典数学家Lars Garding名著<Encounter with Mathematics>. 1. 矩阵的 ...

  2. Latex作者单位的写法—AND 首页脚注

    IEEE会议的模板 以四个作者为例 正常: 作者单位如果名字较短,可以直接写在作者对应的下面,邮箱可以对应写在再接下来的下面. 一 如果邮箱较长,可以用\thanks{ }命令将其变为脚注.例如: ~ ...

  3. SpringMVC之HandlerMethodArgumentResolver和<mvc:argument-resolvers>

    SpringMVC提供了一个HandlerMethodArgumentResolver接口可以让我们处理方法的参数,和注解结合提来,能有很强大的功能,例如SpringMVC提供的@ModelAttri ...

  4. IE8 通过Jquery动态修改html不显示的问题

    for (var i = 0; i < _priority_transf.length && i < xmlList.length; i++) { if (textCont ...

  5. getaddrinfo详解

    #include <sys/socket.h> #include <netdb.h> int getaddrinfo(const char *restrict nodename ...

  6. 自建一个Java Spring MVC项目

    用IDEA Intellij,本来创建的是SpringMVC项目,但是下载的时候,太慢了.所以还是用的Maven项目. 选择Maven 项目->Archetype->Web applica ...

  7. CC+语言 struct 深层探索——CC + language struct deep exploration

    1        struct 的巨大作用 面对一个人的大型C/C++程序时,只看其对struct 的使用情况我们就可以对其编写者的编程经验进行评估.因为一个大型的C/C++程序,势必要涉及一些(甚至 ...

  8. 设备树(Device Tree)

    设备树介绍: 设备树是一个描述设备硬件资源的文件,该文件是由节点组成的树形结构.如下: / { node1 { a-string-property = "A string"; a- ...

  9. json 数组操作

    用js有很久了,但都没有深究过js的数组形式.这段时间做的一个项目,用到数组的地方很多,自以为js还可以的自己居然无从下手,一下狠心,我学!呵呵. 1.数组的创建 var arrayObj = new ...

  10. Forms.Timer、Timers.Timer、Threading.Timer的研究

    .NET Framework里面提供了三种Timer System.Windows.Forms.Timer System.Timers.Timer System.Threading.Timer 一.S ...