AbstractQueuedSynchronizer是JAVA并发包的核心组件,AbstractQueuedSynchronizer中封装了对锁的操作。锁主要有两种操作:获取和释放。获取锁首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败;释放锁就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。要支持这两个操作,需要有一个有序的队列,JAVA中,有序队列是由一个个node组成的,刚看了Node的源码,作个笔记。
这个有序队列是一种CLH(Craig, Landin, and Hagersten)队列,整个是一个包含头结点的双向链表,每个结点其实对应着一个线程,前趋结点上含有控制信息,有一个状态字段waitStatus,这个字段不同的取值代表不同的含义,特别当waitStatus=-1的时候,表示当前结点在释放锁或者被中断的时候,需要唤醒它的后继结点。注意:waitStatus并不会控制一个线程是否获取锁,只是控制线程获取锁的机会,至于被唤醒的线程是否真的获取锁,要看它的造化。
和数据结构中的队列的操作一样,入队时会插入一个新结点,放到队列的尾部,出队时需要修改头结点的next指针,出队需要做更多的工作就是要重新确定队头结点,可能还要处理因超时或者中断引起的被取消的结点,这些结点是不能作为队头结点的。
当waitStatus=1表示这个结点被取消了,被取消的结点没有获取锁的机会了,如果一个结点被取消了,它的
后继结点必须是未被取消的结点,因此在确定它的后继结点时,可能会跳过n个被取消的结点,也就是说这些被取消的结点最终会脱离列表。
前面讲到双向链表,有next和prev两个指针,正是通过next指针来确定后继接点的,当一个结点的后继结点为空时,这个时候有必要从队列尾部反向查找继任结点。当我们遍历这些被取消的结点的过程中,如果发一个结点被取消了,并不会挂起它,而是让它保持在原来的位置。直到我们找到一个没有被取消的结点。(?)
头结点是一个傀儡结点,我们自己是不能创建傀儡结点的,因为如果没有竞争者(或者说没有线程,没有除头结点以外的结点)时会浪费资源。
我们常说的条件队列也在这个同步队列里面,如图所示橙色框,只是用一个字段(nextWaiter字段)来把这些在等待条件的这些结点串起来,注意条件队列只在互斥的情况下使用。
下来分别解释一下Node的几个字段:
waitStatus:结点的状态字段,取以下值:
SIGNAL(-1):如果后继结点已被挂起,或者将要被挂起,当前结点在释放锁或者被取消时,需要唤醒它的后继结点。为了避免竞争,获取锁时第一步就要通知它的后继结点,然后才是尝试获取锁等操作。
CANCELLED(1):指示这个结点因为某种原因(超时或者中断)被取消了,
一旦结点处于该状态,其状态就不能被改变,特别是不可能再被阻塞。
CONDITION(-2):指示这个结点在条件队列中,在满足条件之前,它不会被当成同步队列中的结点。满足条件之后它的值为变为0.
0:None of the above
对于一个正常的结点,该值被设置为0,如果该结点属于条件队列,则被设置为CONDITION,修改status需要采用CAS操作(或者说,原子修改)。
prev字段:指向前趋结点,因为当前结点依赖于前趋结点。入队时设置这个字段,在出队之后为NULL.当前趋结点被取消时,需要找一个非取消的后继结点,这些被取消的结点就会被短路。对于一个结点来说,prev始终有值,因为有头结点的存在,而且头结点永远不会被取消。一个结点只有在获取锁之后才会处于队头,被取消的线程永远不会成功获取锁。
next字段:指向继任结点,当前结点在释放锁时,可能会通知它的后继结点。在入队时分配该字段,出队列时为空。入队时只是分配并不会设置该字段,当有新的结点附在它的后面时才会设置。所以当我们看到一个结点的next域为NULL,并不代表它处于队列尾部。当next域为NULL时,我们需从队尾向前找它的后继结点。被取消的结点它的next域指向自己,而不是设置为NULL。
thread字段:入队时,线程都附着在结点上。结点被用过之后,thread为NULL。
nextWaiter:对于条件队列来说,它的值指向下一个等待条件的结点。因为条件队列只会在互斥模式下有效,我们正是用这个字段来串起这些条件结点的。这些结点在等到条件后会被转移到同步队列重新获取锁。由于条件队列只会在互斥模式下使用,可以把这个字段设置为SHARD来代表共享模式。
具体的获取锁,释放锁操作,共享与互斥等下次再写。
分享到:
相关推荐
在 AQS 中, Node 结点用于封装每一个等待获取资源的线程,包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。waitStatus 变量则表示当前 Node 结点的等待状态,共有五种取值:...
《AQS同步器与Redisson锁在Java高并发API及SpringBoot中的应用》 在Java并发编程领域,AbstractQueuedSynchronizer(AQS)是一个非常重要的基础组件,它是Java并发包java.util.concurrent中实现锁和同步器的核心...
AQS全称为AbstractQueuedSynchronizer,是java中用于构建锁以及其他同步器的一个框架。在多线程的编程中,同步问题是一个非常重要的问题,而AQS正是为了解决这个问题而生的。 首先,我们需要了解的是AQS的核心思想...
### JDK_AQS解析 #### 概述 在Java并发编程中,`AbstractQueuedSynchronizer`(简称AQS)是实现锁和其他同步工具的基础框架。AQS位于`java.util.concurrent`包下,通过模板方法设计模式实现了锁的底层机制。本文将...
java锁AQS基础逻辑
3. **AQS(AbstractQueuedSynchronizer)**:尽管AQS本身不是用于无锁编程的技术,但它提供了一种灵活的方式来构建锁和其他同步工具,也可以作为理解和实现无锁数据结构的基础之一。 4. **无循环遍历**:为了进一步...
HMC253AQS24 &253AQS24E中文数据手册
从JUC中的AQS引入,讲解Java volatile与AQS锁内存可见性
juc 的aqs介绍。
AQS流程图ReentranLock.vsdx
AQS源码分析一、锁的介绍1.1 乐观锁/悲观锁1.2 共享锁/独占锁1.3 公平锁/非公平锁1.4 小结二、AQS框架结构介绍2.1 类图2.2 AQS数据结构三、源码详解3.1 acquire源码详解3.2 release源码详解四、从ReentranLock看公平...
**AQS的数据结构——节点和同步队列** AQS内部维护了一个双端链表,表示同步队列。每个节点代表一个线程,节点的状态包括等待、已取消、已唤醒等。当线程获取锁失败时,会被添加到队列尾部,按照FIFO规则等待。 **...
4. **CLH锁队列**:AQS的等待队列采用CLH(Craig, Landin, and Hagersten)锁队列结构,这是一种高效的自旋锁实现,每个节点代表一个线程,前驱节点即为阻塞它的线程。 5. **节点状态**:节点状态包括PARKED(已...
AQS抽象队列同步器,AQS抽象队列同步器
作为J.U.C的核心组成部分,AQS提供了一种基于FIFO(First In First Out)队列的数据结构,用于构建各种锁或同步工具的基础框架。 #### AQS底层数据结构 AQS底层采用了双向链表作为数据结构,这种结构可以视为一种...
2. **等待队列**:AQS使用CLH(Craig, Landin, and Hagersten)队列锁作为其数据结构,这是一个虚拟的双向队列。当线程无法立即获取资源时,会被构造成队列中的节点加入等待。队列的头部(head)主要负责后续调度,...
一、AQS基本结构与原理 AQS的核心是一个int类型的state字段,它表示资源的状态。当state为0时,表示资源可获取;非0则表示已被占用。AQS维护了一个FIFO的双端等待队列,用于管理等待获取资源的线程。这个队列是由...