摘要:二什么是同步队列同步队列一个双向队列,队列中每个节点等待前驱节点释放共享状态锁被唤醒就可以了。三入列操作如上图了解了同步队列的结构,我们在分析其入列操作在简单不过。
一、写在前面
在上篇我们聊到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指向新头节点并断开老头节点联系就可以了。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/74241.html
摘要:造成当前线程在接到信号被中断或到达指定最后期限之前一直处于等待状态。该线程从等待方法返回前必须获得与相关的锁。如果线程已经获取了锁,则将唤醒条件队列的首节点。 一、写在前面 在前几篇我们聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源码解读,具体参见专栏 《非学无以广才》 这章我们一起聊聊显示的Condition 对象。 ...
摘要:本章我们主要聊独占式即同一时刻只能有一个线程获取同步状态,其它获取同步状态失败的线程则会加入到同步队列中进行等待。到这独占式获取同步和释放同步状态的源码已经分析完了。 一、写在前面 上篇文章通过ReentrantLock 的加锁和释放锁过程给大家聊了聊AQS架构以及实现原理,具体参见《J.U.C|AQS的原理》。 理解了原理,我们在来看看再来一步一步的聊聊其源码是如何实现的。 本章给...
摘要:接着线程过来通过方式获取锁,获取锁的过程就是通过操作变量将其值从变为。线程加锁成功后还有一步重要的操作,就是将设置成为自己。线程屁颠屁颠的就去等待区小憩一会去了。 一、写在前面 这篇文章,我们聊一聊Java并发中的核武器, AQS底层实现。 不管是工作三四年、还是五六年的在工作或者面试中涉及到并发的是时候总是绕不过AQS这个词。 首先,确实还有很多人连AQS是什么都不知道,甚至有的竟...
摘要:二什么是重入锁可重入锁,顾名思义,支持重新进入的锁,其表示该锁能支持一个线程对资源的重复加锁。将由最近成功获得锁,并且还没有释放该锁的线程所拥有。可以使用和方法来检查此情况是否发生。 一、写在前面 前几篇我们具体的聊了AQS原理以及底层源码的实现,具体参见 《J.U.C|一文搞懂AQS》《J.U.C|同步队列(CLH)》《J.U.C|AQS独占式源码分析》《J.U.C|AQS共享式源...
摘要:在时,引入了包,该包中的大多数同步器都是基于来构建的。框架提供了一套通用的机制来管理同步状态阻塞唤醒线程管理等待队列。指针用于在结点线程被取消时,让当前结点的前驱直接指向当前结点的后驱完成出队动作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首发于一世流云的专栏:https://segmentfau...
阅读 2905·2021-11-15 18:02
阅读 3800·2021-10-14 09:43
阅读 3732·2021-09-08 10:41
阅读 2522·2019-08-30 15:53
阅读 1803·2019-08-30 14:14
阅读 1943·2019-08-29 16:12
阅读 3137·2019-08-29 14:03
阅读 1280·2019-08-29 13:46