AQS 同步队列 简单分析

本文深入剖析了AbstractQueuedSynchronizer(AQS)的源码,详细解释了独占锁和共享锁的工作原理,包括acquire、acquireQueued等关键函数的执行流程,以及等待队列的管理机制。

源码AbstractQueuedSynchronizer.java
同步队列 入口函数为 acquire 调用 独占锁:
如果tryAcquire失败.那么会一直等待直到成功.是一种阻塞锁.
在这里插入图片描述
因为acquire是一个阻塞锁.所以队列中有多个等待者,就有多少个线程.大体来说下过程
1.首先某个线程发起acquire获取锁的请求.如果成功很简单资源加锁就完了.主要是失败的处理.
2.如果请求锁失败,那么这时候会新生成一个Node对象包裹请求参数.加到队列尾部,值得注意的是这个队列是一个FIFO,有两个Node全局变量一个记录着head,一个记录着tail.
3.新加入的Node把prev指向tail,tail把next指向Node,最后把Node赋值给tail
4.每个等待的node对象.都在不停的判断它的前一个Node是不是head,如果是则它就不停的尝试获取锁(head拥有锁,只有等到它释放)
5.如图第二个节点,如果能成功的获取资源锁,则证明head已经释放了锁.那么这个时候第二个节点就把不客气的把自己弄成head.那么它的prev指向肯定就得为null.而之前的head这个时候已经没什么用了.把它的next指向质为null.这样互相引用就没了,应该更容易GC了.虽然GC能够处理这种相互引用,但是应该有代价吧.
6.重复上述过程. 下面对代码截图,对没说到的部分再说下在这里插入图片描述
起点函数就是acquire,通过它来获取锁.
tryAcquire是个空函数.需要继承者来重载,这里来看下acquireQueued实现在这里插入图片描述
第一个if基本就是上面说的那些.看第二个if高亮的函数定义在这里插入图片描述
这里上面就没说到.waitStatus是一个等待状态,这里new Node对象里并没有赋值,那么默认值就这0.看它的几种状态的定义在这里插入图片描述
看到基本都是小于0的,只有一个CANCEL = 1 .要注意的是,这个CANCEL是程序内部自己赋值的,也就是说当前线程被终止了.这个由cancelAcquire方法赋值.这个方法是个私有方法.而且从原理上不允许通过acquire后,再手动通过cancelAcquire取消调用.只能一直等待.否则的话这个node的for死循环线程一直存在,造成内存问题.

另外acquire在执行过程中是不能被中断的.有中断只会被置标记.执行完之后,会中断我们调用acquire那个线程.
所以一旦线程调用了acquire来请求锁,那么线程将一直阻塞直到获取锁.这期间没有任何办法来打断这个逻辑.所以如果我们想能够控制获取锁的时机,怎么办呢.那就是AQS的条件队列了.请看下篇的条件队列分析.

用acquireInterruptibly来中断,这个采用捕获InterruptedException这个异常,来达到中断的目地.

共享锁acquireShared .原理同上,只不过,独占锁,资源只有一个.而共享锁,有拥有多个资源.在资源没用完之前不会进行等待队列.资源用完时,一样处理.

<think> 嗯,用户想了解ReentrantLock和Synchronized的底层实现对比,同时关注AQS如何管理线程阻塞队列。这个问题涉及到Java并发编程的核心机制,需要拆解为三个关键部分:锁实现原理对比、阻塞队列管理机制以及具体的线程调度细节。 用户提供的站内引用很有价值,特别是引用[1][2][3]直接提到了ReentrantLock的AQS实现、CLH队列和CAS操作。注意到引用[2]提到"ReentrantLock的锁资源以volatile的state状态描述",这将是解释锁状态管理的切入点。而引用[3]提到的CLH队列改造正是AQS队列的核心特征。 关于实现原理对比,准备从四个维度展开:内存语义层面(Synchronized的monitor与ReentrantLock的volatile+CAS)、阻塞机制(OS内核挂起与JVM层面等待)、可重入实现(monitor计数器与AQS state)、以及功能扩展性。其中要突出引用[1]提到的可中断/公平锁等特性差异。 对于AQS队列管理,需要重点描述三个过程:线程入队时如何通过CAS构建双向链表(引用[3]的Lock-Free操作)、竞争失败线程的park操作(引用[2]的阻塞机制)、以及解锁时的unpark后继节点策略(包括公平与非公平锁的唤醒差异)。CLH队列的变体设计是个关键,要说明AQS如何将原始的CLH自旋锁改造成阻塞锁。 用户可能还隐含了性能对比的需求,虽然没明确问,但应该在总结部分补充场景化建议,比如低竞争用Synchronized(JVM优化)、高竞争用ReentrantLock(可控性),呼应引用[5]提到的并发编程实战经验。最后考虑设计几个源码分析方向的问题,帮助用户深入理解AQS的tryAcquire模板方法模式。 </think> 以下是 `ReentrantLock` 与 `Synchronized` 的底层实现对比及 AQS 管理线程阻塞队列的详细分析: --- ### **一、底层实现对比 (`ReentrantLock` vs `Synchronized`)** | **特性** | **`Synchronized`** | **`ReentrantLock`** | |------------------------|----------------------------------------------------------|----------------------------------------------------------| | **实现机制** | JVM 层面的 `monitorenter`/`monitorexit` 字节码指令 | 基于 AQS 框架的 Java API 实现(`AbstractQueuedSynchronizer`) | | **锁获取方式** | 隐式获取(自动加锁/解锁) | 显式调用 `lock()`/`unlock()` | | **锁状态存储** | 对象头的 Mark Word(存储锁标志、偏向线程ID等) | AQS 的 `volatile int state`(CAS 更新锁状态) | | **可重入性** | 通过计数器实现(`_recursions` 字段) | 通过 `state` 记录重入次数 | | **公平性** | 仅非公平锁 | 支持 **公平锁**(`FairSync`)和 **非公平锁**(`NonfairSync`) | | **中断响应** | 阻塞时不可中断 | 支持 `lockInterruptibly()`(可响应中断) | | **条件变量** | 单条件(`wait()`/`notify()`) | 多条件(`Condition` 接口,支持多个等待队列) | | **性能优化** | JDK 6+ 引入锁升级(无锁→偏向锁→轻量锁→重量锁) | 自旋 + CAS + CLH 队列(避免上下文切换) | | **底层队列** | ObjectMonitor 的 EntryList/CXQ(由 JVM 管理) | AQS 的 CLH 变体队列(双向链表,Java 实现) | > **关键差异**: > - **`Synchronized`** 依赖 JVM 内置锁机制,在竞争激烈时升级为重量级锁(OS 内核态阻塞); > - **`ReentrantLock`** 通过 AQS 的 CAS + CLH 队列实现用户态锁竞争,减少内核切换开销[^2][^3]。 --- ### **二、AQS 管理线程阻塞队列的核心机制** **AQS(AbstractQueuedSynchronizer)** 是 Java 并发包的核心框架,其阻塞队列管理流程如下: #### **1. 队列结构** - **CLH 变体队列**:双向链表(`Node` 节点),包含 `head` 和 `tail` 指针[^3]。 - **节点状态**: ```java volatile int waitStatus; // 取值:CANCELLED(1), SIGNAL(-1), CONDITION(-2) ``` #### **2. 入队流程(竞争锁失败时)** 1. **构建节点**:将当前线程封装为 `Node` 节点; 2. **CAS 入队尾**:通过 `compareAndSetTail()` 原子操作添加到队列尾部; 3. **挂起线程**:调用 `LockSupport.park()` 阻塞当前线程[^2][^3]。 #### **3. 出队与唤醒(锁释放时)** 1. **更新头节点**:释放锁的线程将 `head` 指向后继节点; 2. **唤醒后继**:若后继节点状态为 `SIGNAL`,则调用 `LockSupport.unpark()` 唤醒其线程; 3. **CAS 竞争**:被唤醒的线程尝试 CAS 获取锁,失败则重新挂起。 #### **4. 公平锁 vs 非公平锁** - **公平锁**:直接检查队列是否为空(`hasQueuedPredecessors()`),若有等待线程则入队; - **非公平锁**:先尝试 CAS 抢锁,失败再入队(可能插队)[^1][^2]。 ```mermaid graph TD A[线程尝试获取锁] -->|成功| B[执行临界区] A -->|失败| C[AQS 入队] C --> D[线程挂起] D -->|锁释放| E[唤醒队首节点] E --> F[尝试 CAS 抢锁] F -->|成功| B F -->|失败| D ``` --- ### **三、关键设计优势** 1. **CAS 无锁化操作**:队列的入队/出队通过 CAS 实现线程安全,避免阻塞[^3]; 2. **状态分离**:`state` 字段独立控制锁状态,与队列解耦; 3. **条件变量分离**:每个 `Condition` 维护独立等待队列,与 AQS队列协同[^2]; 4. **可扩展性**:AQS 模板方法(如 `tryAcquire`)支持自定义同步器(如 `Semaphore`)[^4]。 --- ### **总结** - **`Synchronized`**:简单高效,适合低竞争场景(JVM 自动优化锁升级); - **`ReentrantLock`**:功能丰富(可中断、公平锁、多条件),适合高竞争或复杂同步需求; - **AQS 队列**:通过 CLH 变体 + CAS + `LockSupport` 实现高效阻塞管理,减少内核切换[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bruk_spp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值