当前位置: 首页 > news >正文

深入解析 Java AQS (AbstractQueuedSynchronizer) 的实现原理

1. 引言

Java 并发包 (java.util.concurrent.locks) 中的 ReentrantLockSemaphoreCountDownLatch 等同步工具的核心实现都依赖于 AQS (AbstractQueuedSynchronizer)。AQS 提供了一套通用的同步框架,通过 FIFO 队列 管理线程的排队与唤醒,并支持 独占锁共享锁 两种模式。

本文将深入分析 AQS 的实现原理,包括:

  • AQS 的核心数据结构
  • 同步队列(CLH 变体)的实现
  • 线程的入队、阻塞、唤醒机制
  • 公平锁与非公平锁的实现差异
  • AQS 的模板方法设计
  • 关键源码分析 + 流程图

2. AQS 的核心结构

AQS 的核心依赖以下几个关键组件:

  1. volatile int state:同步状态,不同的锁有不同的语义:
    • ReentrantLockstate 表示锁的重入次数(0 表示未锁定,1 表示被占用,>1 表示重入)。
    • Semaphorestate 表示剩余的许可数量。
    • CountDownLatchstate 表示剩余的计数。
  2. 双向同步队列(CLH 变体):存储等待获取锁的线程。
  3. Node 节点:封装线程和等待状态。

2.1 AQS 同步队列结构

AQS 使用 双向链表 实现同步队列,每个节点是一个 Node 对象:

static final class Node {volatile int waitStatus;  // 节点状态(CANCELLED, SIGNAL, CONDITION, PROPAGATE)volatile Node prev;       // 前驱节点volatile Node next;       // 后继节点volatile Thread thread;   // 关联的线程Node nextWaiter;          // 用于条件队列或共享模式
}

waitStatus 的取值:

  • CANCELLED (1):线程已取消等待(超时或中断)。
  • SIGNAL (-1):当前节点的后继节点需要被唤醒。
  • CONDITION (-2):节点在条件队列中(如 Condition.await())。
  • PROPAGATE (-3):共享模式下,唤醒需要传播。

headtail

  • head:虚拟头节点,不存储线程,仅用于管理队列。
  • tail:指向队列的最后一个节点。

同步队列示例:

head (dummy) → Node(thread1, SIGNAL) → Node(thread2, SIGNAL) → tail

3. AQS 的关键流程

3.1 获取锁(acquire)

ReentrantLock 为例,调用 lock() 最终会进入 AQS 的 acquire(1)

public final void acquire(int arg) {if (!tryAcquire(arg) &&      // 尝试获取锁(子类实现)acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 失败则入队selfInterrupt();          // 如果被中断,恢复中断状态
}

流程详解:

  1. tryAcquire(arg)(子类实现):
    • 非公平锁:直接尝试 CAS 修改 state,成功则获取锁。
    • 公平锁:先检查是否有前驱节点 (hasQueuedPredecessors()),再尝试 CAS。
  2. addWaiter(Node.EXCLUSIVE)
    • 将当前线程包装成 Node,CAS 插入队尾。
  3. acquireQueued(node, arg)
    • 自旋检查前驱是否是 head(即自己是第一个等待者)。
    • 如果是,再次尝试 tryAcquire
    • 如果不是,调用 shouldParkAfterFailedAcquire() 判断是否需要阻塞:
      • 前驱 waitStatus=SIGNAL → 可以安全阻塞(前驱释放时会唤醒自己)。
      • 前驱 waitStatus=CANCELLED → 跳过取消的节点。
    • 调用 LockSupport.park() 阻塞线程。

流程图:

graph TDA[线程调用 lock()] --> B{tryAcquire 成功?}B -->|是| C[获取锁, 直接执行]B -->|否| D[addWaiter 入队]D --> E[acquireQueued 自旋检查]E --> F{前驱是 head?}F -->|是| G[tryAcquire 尝试获取锁]G -->|成功| H[设置自己为 head, 执行]G -->|失败| I[shouldParkAfterFailedAcquire]F -->|否| II --> J[LockSupport.park 阻塞]

3.2 释放锁(release)

调用 unlock() 最终进入 release(1)

public final boolean release(int arg) {if (tryRelease(arg)) {       // 子类实现释放逻辑Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);   // 唤醒后继节点return true;}return false;
}

流程详解:

  1. tryRelease(arg)(子类实现):
    • 修改 state,如果 state=0,表示完全释放。
  2. unparkSuccessor(h)
    • 找到 head 后第一个未被取消的节点。
    • 调用 LockSupport.unpark(node.thread) 唤醒线程。

唤醒后的行为:

  • 被唤醒的线程回到 acquireQueued 的循环,再次尝试 tryAcquire
  • 如果成功,它将成为新的 head,并继续执行。

4. 公平锁 vs 非公平锁

4.1 非公平锁(默认)

特点:

  • 新线程可以“插队”,直接尝试 CAS 获取锁,无需排队。
  • 吞吐量更高,但可能导致“饥饿”。

实现 (NonfairSync):

final void lock() {if (compareAndSetState(0, 1))  // 先尝试插队setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);  // 失败再走正常流程
}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);  // 不检查队列,直接 CAS
}

4.2 公平锁

特点:

  • 严格按照 FIFO 顺序获取锁,新线程必须排队。
  • 避免“饥饿”,但性能较低。

实现 (FairSync):

protected final boolean tryAcquire(int acquires) {if (!hasQueuedPredecessors() &&  // 检查是否有前驱节点compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}return false;
}

对比总结:

特性非公平锁公平锁
获取策略允许插队严格 FIFO
性能更高(减少线程切换)较低(严格排队)
适用场景高并发、锁竞争激烈需要严格顺序的场景

5. 总结

  • AQS 的核心state + CLH 变体队列 + LockSupport 阻塞/唤醒。
  • 同步队列管理:通过 Node 节点、head/tail 指针实现线程排队。
  • 公平性控制:由 tryAcquire 是否检查队列决定。
  • 适用场景
    • 非公平锁:大多数高并发场景(如 ReentrantLock 默认)。
    • 公平锁:需要严格顺序的场景(如任务调度)。

AQS 是 Java 并发包的基石,理解它的实现有助于深入掌握 ReentrantLockSemaphoreCountDownLatch 等同步工具的工作原理。


文章转载自:
http://annihilationism.tmizpp.cn
http://blackcap.tmizpp.cn
http://canopy.tmizpp.cn
http://ageing.tmizpp.cn
http://automaton.tmizpp.cn
http://ahuehuete.tmizpp.cn
http://canalboat.tmizpp.cn
http://chanfron.tmizpp.cn
http://autocratically.tmizpp.cn
http://antifebrin.tmizpp.cn
http://aeroelastics.tmizpp.cn
http://abhorrer.tmizpp.cn
http://chorine.tmizpp.cn
http://batten.tmizpp.cn
http://bunkmate.tmizpp.cn
http://abbreviate.tmizpp.cn
http://aeolotropy.tmizpp.cn
http://cay.tmizpp.cn
http://apf.tmizpp.cn
http://catalyzer.tmizpp.cn
http://absorber.tmizpp.cn
http://casing.tmizpp.cn
http://anodal.tmizpp.cn
http://carburetion.tmizpp.cn
http://caliche.tmizpp.cn
http://battlewagon.tmizpp.cn
http://bergsonian.tmizpp.cn
http://bauxite.tmizpp.cn
http://amperage.tmizpp.cn
http://bijection.tmizpp.cn
http://www.dtcms.com/a/280859.html

相关文章:

  • LeetCode 3136.有效单词:遍历模拟
  • [实战] 基8 FFT/IFFT算法原理与实现(完整C代码)
  • 【每天一个知识点】多模态信息(Multimodal Information)
  • 【知识扫盲】tokenizer.json中的vocab和merges是什么?
  • 【机器学习】第二章 Python入门
  • 【Unity】MiniGame编辑器小游戏(十四)基础支持模块(游戏窗口、游戏对象、物理系统、动画系统、射线检测)
  • 数学中的教学思想
  • MySQL 8.0 OCP 1Z0-908 题目解析(24)
  • P3842 [TJOI2007] 线段
  • Sharding-JDBC 分布式事务实战指南:XA/Seata 方案解析
  • sqli-labs靶场通关笔记:第18-19关 HTTP头部注入
  • 【C++】初识C++(1)
  • 课题学习笔记1——文本问答与信息抽取关键技术研究论文阅读(用于无结构化文本问答的文本生成技术)
  • Java 大视界 -- Java 大数据机器学习模型在金融风险传染路径分析与防控策略制定中的应用(347)
  • QT——QList的详细讲解
  • Redis的下载安装+基础操作+redis客户端的安装
  • 使用 1Panel PHP 运行环境部署 WordPress
  • 辨析git reset三种模式以及和git revert的区别:回退到指定版本和撤销指定版本的操作
  • 零样本轴承故障诊断SC - GAN模型
  • 【PCIe 总线及设备入门学习专栏 5.1.2 -- PCIe EP core_rst_n 与 app_rst_n】
  • React-router
  • 未来大模型在中小型企业如何实现普及
  • PG备份一(逻辑备份)
  • Kafka——生产者消息分区机制原理剖析
  • Java基础教程(009): Java 的封装
  • Samba配置使用
  • 算法学习笔记:23.贪心算法之活动选择问题 ——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • 重学前端005 --- 响应式网页设计 CSS 盒子模型
  • Python函数进阶
  • python 基于 httpx 的流式请求