一、写在前面

在上篇我们聊到AQS的原理,具体参见《J.U.C|AQS原理》。

这篇我们来给大家聊聊AQS中核心同步队列(CLH)。

二、什么是同步队列(CLH)

同步队列

一个FIFO双向队列,队列中每个节点等待前驱节点释放共享状态(锁)被唤醒就可以了。

AQS如何使用它?

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

Node节点面貌?

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调用singnal()方法后,该节点加入到同步队列中。 */

static final int CONDITION = -2;

/**

* 表示下一次共享式获取同步状态的时会被无条件的传播下去。

*/

static final int PROPAGATE = -3;

/**等待状态*/

volatile int waitStatus;

/**前驱节点 */

volatile Node prev;

/**后继节点*/

volatile Node next;

/**获取同步状态的线程 */

volatile Thread thread;

/**链接下一个等待状态 */

Node nextWaiter;

// 下面一些方法就不贴了

}

CLH同步队列的结构图

这里是基于CAS(保证线程的安全)来设置尾节点的。

三、入列操作

如上图了解了同步队列的结构, 我们在分析其入列操作在简单不过。无非就是将tail(使用CAS保证原子操作)指向新节点,新节点的prev指向队列中最后一节点(旧的tail节点),原队列中最后一节点的next节点指向新节点以此来建立联系,来张图帮助大家理解。

源码

源码我们可以通过AQS中的以下两个方法来了解下

addWaiter方法

private Node addWaiter(Node mode) {

// 以给定的模式来构建节点, mode有两种模式

// 共享式SHARED, 独占式EXCLUSIVE;

Node node = new Node(Thread.currentThread(), mode);

// 尝试快速将该节点加入到队列的尾部

Node pred = tail;

if (pred != null) {

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

// 如果快速加入失败,则通过 anq方式入列

enq(node);

return node;

}

先通过addWaiter(Node node)方法尝试快速将该节点设置尾成尾节点,设置失败走enq(final Node node)方法

enq

private Node enq(final Node node) {

// CAS自旋,直到加入队尾成功

for (;;) {

Node t = tail;

if (t == null) { // 如果队列为空,则必须先初始化CLH队列,新建一个空节点标识作为Hader节点,并将tail 指向它

if (compareAndSetHead(new Node()))

tail = head;

} else {// 正常流程,加入队列尾部

node.prev = t;

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

通过“自旋”也就是死循环的方式来保证该节点能顺利的加入到队列尾部,只有加入成功才会退出循环,否则会一直循序直到成功。

上述两个方法都是通过compareAndSetHead(new Node())方法来设置尾节点,以保证节点的添加的原子性(保证节点的添加的线程安全。)

四、出列操作

同步队列(CLH)遵循FIFO,首节点是获取同步状态的节点,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单。如下图

设置首节点是通过获取同步状态成功的线程来完成的(获取同步状态是通过CAS来完成),只能有一个线程能够获取到同步状态,因此设置头节点的操作并不需要CAS来保证,只需要将首节点设置为其原首节点的后继节点并断开原首节点的next(等待GC回收)应用即可。

五、总结

聊完后我们来总一下,同步队列就是一个FIFO双向对队列,其每个节点包含获取同步状态失败的线程应用、等待状态、前驱节点、后继节点、节点的属性类型以及名称描述。

其入列操作也就是利用CAS(保证线程安全)来设置尾节点,出列就很简单了直接将head指向新头节点并断开老头节点联系就可以了。

java clh队列_J.U.C|同步队列(CLH)相关推荐

  1. Java Review - 并发编程_抽象同步队列AQS

    文章目录 概述 AQS--锁的底层支持 state 的作用 ConditionObject 独占 VS 共享 独占方式下,获取与释放资源的流程 共享方式下,获取与释放资源的流程 Interruptib ...

  2. 线程状态切换之等待队列和同步队列

    转自:https://blog.csdn.net/weixin_37695911/article/details/106668435 线程的主要状态及切换: 1.初始-NEW(还未调用start()) ...

  3. Java并发编程之AbstractQueuedSychronizer(抽象队列同步器,简称AQS)

    AbstractQueuedSychronizer(抽象队列同步器,简称AQS): JDK的并发包(包名:java.util.concurrent,以下简称JUC)下面提供了很多并发操作的工具类,如: ...

  4. 死磕Java并发:J.U.C之AQS:CLH同步队列

    本文转载自公号:Java技术驿站 在上篇文章"死磕Java并发:J.U.C之AQS简介"中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...

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

    原文出处:https://www.cmsblogs.com/category/1391296887813967872 『chenssy』 在上篇博客[死磕Java并发]-----J.U.C之AQS:A ...

  6. java队列加锁_java并发-----浅析ReentrantLock加锁,解锁过程,公平锁非公平锁,AQS入门,CLH同步队列...

    前言 为什么需要去了解AQS,AQS,AbstractQueuedSynchronizer,即队列同步器.它是构建锁或者其他同步组件的基础框架(如ReentrantLock.ReentrantRead ...

  7. java8 同步队列_秋招之路8:JAVA锁体系和AQS抽象队列同步器

    整个的体系图 悲观锁,乐观锁 是一个广义概念:体现的是看待线程同步的不同角度. 悲观锁 认为在自己使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不被别的线程修改. 实现:关 ...

  8. JAVA写同步栈_tomcat实现的同步队列和同步栈

    tomcat实现的同步队列,同步栈用于数据量比较固定且基本很少删除的场景,尽可能减少内存消耗. 同步队列 /* * Licensed to the Apache Software Foundation ...

  9. java同步队列_Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理

    前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...

最新文章

  1. 区块链课程笔记-第一课哈希算法在加密中的应用
  2. 好程序员大数据教程:SparkShell和IDEA中编写Spark程序
  3. 大话设计模式(六 关于Flex的争论)
  4. Linux的僵尸进程产生原因及解决方法
  5. 【Java 笔记】 java 格式化输出
  6. jzoj1471-Y【bitset,dp,状压】
  7. ubuntu修改字体 样式
  8. as3:sprite作为容器使用时,最好不要指定width,height
  9. 程序员父亲的遗产——编程十诫 转载
  10. keygenerator php,学习PHP生成器的使用
  11. kotlin 复制对象属性_Kotlin面向对象编程笔记
  12. [POJ3580]SuperMemo
  13. 重庆航天职业技术学院计算机系在哪个校区,2020年重庆航天职业技术学院地址在哪里...
  14. 线性回归2——正则化(含代码)
  15. 前端面试题汇总(vue+html基础)最新最全
  16. Conda的PyTorch安装
  17. input选择框样式修改与自定义
  18. 报告|中国智能音箱已入局全球市场,双重商业模式迅速扩张
  19. 前端JavaScript的学习笔记
  20. 怎么查找iphone测试的软件,iphone怎么测试跑分 iphone跑分软件使用教程

热门文章

  1. 记一次 关于Android studio 编译报错compileDebugJavaWithJavac FAILED
  2. 足球相关的英文专业术语(持续更新中...Ctrl+F可直接进行搜索)
  3. 51单片机控制LCD1602模块
  4. 讲讲多拨的额外骚操作(多拨附加教程)
  5. ACM图论之存图方式
  6. 开脑洞:数字文明的等级
  7. 路由之HSRP热备份
  8. php中baseasset,放映员资料库 ~ 影院之家 | The projectionist database - CineAsset 5.2.11 for Win 下载...
  9. 用python操作浏览器的三种方式
  10. JWT 做 token 签发