005-从0到1带你深入并发编程:AQS acquire方法深度解析:从锁获取到线程阻塞的完整流程
本文深入解析了AQS(AbstractQueuedSynchronizer)中acquire方法的实现原理,揭示了Java并发锁获取的核心机制。文章通过四步曲详细剖析了该方法:快速获取尝试(tryAcquire)、线程包装与入队(addWaiter)、队列等待与重试(acquireQueued)以及中断处理(selfInterrupt)。重点解读了源码中精妙的设计思想,包括快速路径优化、CAS操作
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥 有兴趣可以联系我
🔥🔥🔥 文末有往期免费源码,直接领取获取(无删减,无套路)
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
《AQS acquire方法深度解析:从锁获取到线程阻塞的完整流程》
《逐行解读AQS acquire源码:tryAcquire、入队、自旋与中断处理的精妙设计》
《AQS acquire方法四步曲:掌握Java并发锁获取的核心逻辑》
正文
一、acquire方法:独占锁获取的入口
在AQS(AbstractQueuedSynchronizer)的架构中,acquire(int arg)方法是独占式获取资源的顶层入口。这个方法虽然代码量不多,但凝聚了AQS设计的核心思想,是整个同步器框架的精华所在。
acquire方法采用模板方法模式,定义了获取资源的整体流程,而将具体的资源获取逻辑交由子类实现。这种设计既保证了流程的一致性,又提供了足够的灵活性。
二、acquire方法源码全景
让我们首先从宏观角度理解acquire方法的整体结构:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这段简洁的代码包含了四个关键步骤,通过逻辑运算符的短路特性巧妙地连接在一起。整个方法体现了"快速路径优先,慢速路径兜底"的设计哲学。
三、第一步:tryAcquire - 快速获取尝试
tryAcquire(arg)是acquire流程的第一个环节,也是子类必须实现的核心方法:
3.1 方法签名与职责
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
tryAcquire的职责是根据具体的同步语义尝试获取资源。在ReentrantLock中,它需要处理:
-
锁的初始获取(state从0到1)
-
锁的重入(当前线程已持有锁)
-
公平性检查(是否有前驱节点在等待)
3.2 快速路径的优势
将tryAcquire放在第一步体现了重要的性能优化思想:
-
无竞争场景:大多数情况下锁是可用的,直接获取成功
-
避免入队开销:不需要创建节点、操作队列
-
减少上下文切换:线程不会被阻塞
统计表明,在大多数应用中,超过90%的锁获取操作在tryAcquire阶段就成功了,这证明了快速路径设计的价值。
四、第二步:addWaiter - 线程包装与入队
当tryAcquire失败后,线程需要进入等待队列。addWaiter(Node.EXCLUSIVE)负责这个关键转换:
4.1 节点创建与模式
private Node addWaiter(Node mode) {
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;
}
}
// 完整入队流程
enq(node);
return node;
}
节点模式的含义:
-
Node.EXCLUSIVE:独占模式,用于ReentrantLock等 -
Node.SHARED:共享模式,用于Semaphore、CountDownLatch等
4.2 入队算法的精妙设计
addWaiter采用了"快速路径+完整路径"的双重策略:
快速路径:当队列已初始化时,直接CAS尝试设置尾节点 完整路径:通过enq方法处理队列初始化或CAS竞争失败
这种设计避免了在无竞争情况下执行完整的入队逻辑,提升了性能。
4.3 enq方法的可靠性保证
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列未初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法通过无限循环确保节点最终成功入队,这种"乐观重试"策略是AQS高可靠性的基础。
五、第三步:acquireQueued - 队列中的等待艺术
这是acquire方法中最复杂、最精妙的部分,线程在队列中等待并适时尝试获取资源:
5.1 方法结构与核心循环
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
5.2 关键问题:为什么阻塞前要再次尝试获取?
这是acquireQueued设计中最值得深思的问题。在调用parkAndCheckInterrupt()进入阻塞之前,线程会执行:
-
检查前驱节点是否为头节点(
p == head) -
如果是,再次尝试获取资源(
tryAcquire(arg))
这样设计的好处:
1. 减少不必要的阻塞 如果前驱节点刚好释放锁,当前线程可以直接获取,避免线程切换的开销。考虑这样的时序:
-
线程A(头节点)释放锁
-
线程B(第二个节点)正准备阻塞
-
线程B检查发现自己是头节点的后继,尝试获取成功 避免了线程B的阻塞和后续的唤醒操作。
2. 利用时间局部性 锁的持有时间通常很短,前驱节点释放后,锁有很大概率仍然可用。
3. 提升吞吐量 减少了线程状态切换的次数,整体系统吞吐量得到提升。
4. 公平性保证 只有头节点的后继才有资格尝试获取,严格维护了FIFO顺序。
5.3 shouldParkAfterFailedAcquire - 状态管理智慧
这个方法负责设置前驱节点的状态,确保当前线程在适当的时候被唤醒:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
状态处理的三种情况:
-
SIGNAL(-1):前驱节点会通知当前节点,可以安全阻塞 -
CANCELLED(>0):跳过已取消的节点,重新链接队列 -
0或PROPAGATE:CAS设置前驱为SIGNAL状态
5.4 parkAndCheckInterrupt - 阻塞与中断处理
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
这里的设计也很精妙:
-
使用
Thread.interrupted()检查并清除中断状态 -
返回中断状态,但不立即响应
-
在acquire方法最后统一处理中断
六、第四步:selfInterrupt - 中断的延迟处理
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
延迟中断处理的设计考量:
-
保持逻辑清晰:锁获取逻辑与中断处理分离
-
性能优化:在非中断路径中避免不必要的操作
-
语义正确:最终恢复中断状态,不丢失中断信息
七、acquire方法的异常处理与资源清理
acquireQueued方法的finally块体现了AQS的健壮性设计:
finally {
if (failed)
cancelAcquire(node);
}
cancelAcquire负责:
-
清理失败节点的状态
-
维护队列的完整性
-
适时唤醒后继节点
这种设计确保了即使在异常情况下,队列状态也不会被破坏。
八、性能优化深度分析
8.1 自旋次数的权衡
在acquireQueued中,线程在阻塞前会进行有限次数的尝试。这个设计权衡了两种开销:
-
自旋开销:CPU周期浪费在忙等待上
-
阻塞开销:线程切换和调度延迟
通过适度的自旋,在多数情况下找到了最佳平衡点。
8.2 内存屏障的合理使用
在整个acquire流程中,AQS恰当地使用了内存屏障:
-
volatile读写保证状态的可见性 -
CAS操作隐含的内存屏障效果
-
LockSupport.park提供的屏障保证
九、实战启示与最佳实践
理解acquire方法的源码,为我们提供了重要的实践指导:
9.1 自定义同步器实现
实现tryAcquire时应注意:
-
正确使用CAS操作
-
合理处理重入逻辑
-
考虑公平性需求
9.2 性能调优方向
-
减少锁竞争:缩小临界区范围
-
选择合适的锁策略:读写锁、分段锁等
-
监控队列长度:及时发现性能瓶颈
9.3 问题诊断技巧
-
线程长时间阻塞:检查锁竞争情况
-
CAS失败频繁:考虑锁分解
-
队列过长:优化锁粒度
十、总结:精妙设计的启示
AQS的acquire方法体现了多个重要的软件设计原则:
-
分离变与不变:模板方法固定流程,子类实现变化逻辑
-
快速路径优化:优先处理无竞争场景
-
渐进式复杂度:从简单尝试到复杂队列管理
-
资源清理保障:异常情况下的状态维护
通过深入分析acquire方法的每个细节,我们不仅理解了AQS的工作原理,更学到了系统设计的智慧。这种"简单接口背后隐藏复杂实现"的设计哲学,正是构建高质量软件系统的关键。



「在线考试系统源码(含搭建教程)」 (无删减,无套路):🔥🔥🔥
链接:https://pan.quark.cn/s/96c4f00fdb43 提取码:WR6M
往期免费源码对应视频:
免费获取--SpringBoot+Vue宠物商城网站系统
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕网址:扣棣编程,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!
⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇
更多推荐


所有评论(0)