CLH同步队列
创始人
2025-05-31 00:32:20

什么是同步队列(CLH)

同步队列

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

AQS如何使用它?

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

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

CLH同步队列的结构图

在这里插入图片描述
这里是基于CAS(保证线程的安全)来设置尾节点的。

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;// 下面一些方法就不贴了}

入列操作

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

在这里插入图片描述

源码

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)方法

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回收)应用即可。

CLH锁原理

首先有一个尾节点指针,通过这个尾结点指针来构建等待线程的逻辑队列,当有新的节点加入队列时,尾节点指针会指向这个新加入的节点,并将原本的尾节点变为当前新加入节点的前驱节点。因此能确保线程线程先到先服务的公平性,尾指针可以说是构建逻辑队列的桥梁;此外这个尾节点指针是原子引用类型,避免了多线程并发操作的线程安全性问题;
通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量指向自己的前驱节点中的变量,通过不断地自旋,感知到前驱节点的变化后成功获取到锁。

CLH锁的优点

没有惊群效应。假设有1000个线程等待获取锁,锁释放后,只会通知队列中的第一个线程去竞争锁,避免了同时唤醒大量线程 在瞬间争抢CPU资源,避免了惊群效应。(此处仅仅是不会对锁过度的争抢,也就是公平锁的好处。但是自旋锁的实现方式依然消耗CPU)
CLH队列锁的长处是空间复杂度低(假设有n个线程。L个锁,每一个线程每次仅仅获取一个锁,那么须要的存储空间是O(L+n),n个线程有n个myNode。L个锁有L个tail)。

CLH锁的缺点

在NUMA系统结构下性能稍差。在这样的系统结构下,每一个线程有自己的内存,假设前趋结点的内存位置比較远。自旋推断前趋结点的locked域,性能将大打折扣,在SMP结构下还是非常有效的。【CLH自旋在前驱节点上,访问的是其他线程的变量值,在NUMA架构下,其他线程变量有可能是对端CPU的高速缓存,因此更适合SMP架构】

总结

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

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

在lock锁的加锁实现,线程池的实现均利用了同步队列

CLH队列其实属于我们学习AQS的前菜。但是只有深入研究后,才知道CLH存在什么问题(CLH每一个线程都是一个自旋锁,非常消耗CPU),以及AQS在CLH的基础上做了哪些优化。我们可以看到公平锁就是最初的实现理念就是CLH队列。

在队列同步器中,同步队列为什么是双向链表,而等待队列是单链表

在队列同步器中,头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态后,将会唤醒其他后续节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点,如果是则尝试获取同步状态。

所以为了能让后继节点获取到其前驱节点,同步队列便设置为双向链表,而等待队列没有这样的需求,就为单链表。

相关内容

热门资讯

【微服务】—— Nacos设计... 文章目录一、简介Nacos起源Nacos 定位Nacos 优势二、Nacos 总体设计1࿰...
axios 请求其他服务器地址... 场景还原: Vue2项目中在生产环境调用其他服务器请求地址时候会在请求地址默认加上一串当前浏览器域...
一斤便宜10元还要降?榴莲可以... 最近几年,各种知名水果的价格可以说都出现了比较大的变化,特别是最近榴莲的价格持续下降,甚至还有降价的...
图解redis对象(hash ... 哈希表 哈希对象的编码可以是ziplist或者hashtable 条件 ·哈希对象保存的所有键值对的...
C语言再学习 -- C 标准库... 参看:C 标准库 - stdlib.h C 标准库 - 简介 stdlib .h 头...
欧佩克+连续第三次大幅增产,油... 欧佩克连续第三次大幅增产,这一举措无疑给油价带来了巨大压力,恐使其承压下跌。欧佩克作为全球重要的石油...
python 安装包相关命令 查看匹配的版本(大小写敏感)pip -V 查询已经安装了的包,并可以显示相应的版本&...
“2025外贸优品中华行——天... 中新网天津5月31日电 (记者 王君妍)31日,“2025外贸优品中华行—天津站”活动正式启幕。本次...
基于SpringBoot+Vu... 您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦。 &#...
网络技巧|远程桌面连接不上的多... 写在前面的话专注于网络各种技巧和实用工具的分享,都是日常工作中遇到的大大小小问题记录下...
IM即时通讯软件系统源码安卓、... demo软件园每日更新资源,请看到最后就能获取你想要的: ​ 1.《计算机系统结构:解...
讲解一下关于MySQL数据库的... 对于数据库,市面上有着不少的数据库!比如:Oracle数据...
3.1.2数据库体系结构:分布... 3.1.2数据库体系结构:分布式数据库、分布式数据库特点、分布式数据库结构、数据分片、...
学习streamlit-6 系列目录 学习streamlit-1,简介学习streamlit-2,s...
冒泡 VS 插入 VS 选择—... 文章目录什么样的“排序算法”更加优质?排序算法的执行效率排序算法的内存消耗排序算法的稳...
Python 多线程 文章目录一、简介1.1 多线程的特性1.2 GIL二、线程1.2 单线程1.3 多线程三、线程池3....
基于树莓派实现超声波测距 目录 一,写在前面 二,超声波模块说明 ● 模块基本参数 ● IO口接线...
Linux(网络基础---数据... 文章目录0. 前言1. 以太网的帧格式2. 再谈局域网原理3. 汇总整体通信流程,补全...
瑞萨Renesas RA2L1... 前言(1)首先感谢 李肯前辈的活动,从而申请到了RA2L1...
在 Python 中如何删除指... 文章目录删除字符后的所有内容,保留分隔符删除最后一次出现的字符后的所有内容删除最后一次...
如何将字符串反转? 参考答案 使用 StringBuilder 或 StringBuffer 的 reverse 方法&...
初级指针的简单介绍 目录 1.什么是指针? 2.指针和指针类型 2.1指针+-整数 2.2指针的解...
常见的CMS后台getshel... 目录 WordPress dedecms aspcms 南方数据企业系统 phpmyadmin日志 ...
CVPR 2023 | 旷视研... 近日,CVPR 2023 论文接收结果出炉。近年来,CVPR 的投稿数量...
【Linux】进程的概念--程... 语言级别的地址以前我们在学习C语言指针的时候,会打印地址,会有内存级别的...
ChatGPT重量级对手产品:... 什么是ClaudeClaude是下一代人工智能助手,基于 Anthropic 对训练有...
全球与中国乳胶防水涂料市场规模... 全球与中国乳胶防水涂料市场规模预测及未来动向前瞻报告2025-2031年 【全新修订】:2025年...
外国剁手党们,催着中国电商集体... 文 | 潮汐商业评论 同欧美的朋友喜欢催更新中国霸道总裁等短剧一样,海外的剁手党们也爱装满来自中国...
nginx快速入门.跟学B站n... nginx快速入门.跟学B站nginx一小时精讲课程笔记nginx简介及环境准备nginx简介环境准...
C++ static静态成员变... 对象的内存中包含了成员变量,不同的对象占用不同的内存,这使得不同对象的成...