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

对AQS的详解

目录

一、AQS 是什么?

二、核心原理

1. 状态变量 (state)

2. CLH 队列 (同步队列)

3. 两种资源共享模式

三、设计与关键方法:模板方法模式

需要子类实现的方法 (Protected)

重要的模板方法 (Public Final)

四、工作流程详解 (以独占模式为例)

获取锁 (acquire(int arg))

释放锁 (release(int arg))

五、实战:看 ReentrantLock 如何基于 AQS 实现

总结


一、AQS 是什么?

AQS 是一个用于构建同步器框架。它提供了一个基于 FIFO 等待队列的底层同步机制,开发者通过继承 AQS 并实现其少量的模板方法,就可以轻松地构建出各种功能的同步器。

它的核心思想是:

  1. 如果被请求的共享资源是空闲的,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。

  2. 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待、被唤醒时锁分配的机制。这个机制 AQS 是用 CLH 队列锁 来实现的,即将暂时获取不到锁的线程加入到队列中。


二、核心原理

AQS 的核心可以概括为三个部分:

  1. 一个 volatile 的整型状态变量 (state)

  2. 一个 FIFO 线程等待队列 (CLH队列的变体)

  3. 两种资源共享模式:独占 (Exclusive) 和 共享 (Share)

1. 状态变量 (state)

这是一个用 volatile 修饰的 int 成员变量,是 AQS 的灵魂所在。锁的概念很大程度上就是对 state 的操作

状态含义由子类定义:AQS 不关心 state 的具体含义,完全由其子类根据需求决定。例如:

  1. ReentrantLockstate 表示重入次数。0 表示锁空闲,>=1 表示被持有,>1 表示被同一线程重入。
  2. Semaphorestate 表示剩余的许可证数量
  3. CountDownLatchstate 表示还需要等待的计数

所有操作都是原子性的:对 state 的修改都通过 CAS (Compare-And-Swap) 操作来保证原子性和可见性。

2. CLH 队列 (同步队列)

这是一个双向链表结构的队列,用于存放所有等待获取资源的线程。当线程申请资源失败时,AQS 会将其封装成一个 Node 节点,并将其加入队列尾部,同时阻塞该线程(使用 LockSupport.park())。当持有资源的线程释放资源时,会唤醒队列中的下一个节点,使其重新尝试获取资源。

Node 节点的主要属性:

  1. thread: 等待的线程

  2. waitStatus: 节点的状态(如 CANCELLEDSIGNALCONDITION 等)

  3. prevnext: 前驱和后继指针

  4. nextWaiter: 用于表示节点是共享模式还是独占模式

3. 两种资源共享模式
  1. 独占模式 (Exclusive): 资源一次只允许一个线程访问。例如:ReentrantLock

  2. 共享模式 (Share): 资源允许多个线程同时访问。例如:SemaphoreCountDownLatch

不同的模式决定了线程在获取和释放资源时的行为,尤其是在唤醒等待队列中的线程时,共享模式可能会一次性唤醒多个节点。


三、设计与关键方法:模板方法模式

AQS 使用了模板方法模式。它定义了顶级逻辑的骨架(如入队、出队),而将一些关键步骤的实现推迟到子类中。

需要子类实现的方法 (Protected)

这些方法是构建同步器的核心,AQS 会在其内部逻辑中调用它们:

  1. boolean tryAcquire(int arg)独占式获取资源。成功返回 true,失败返回 false。
  2. boolean tryRelease(int arg)独占式释放资源。成功返回 true,失败返回 false。
  3. int tryAcquireShared(int arg)共享式获取资源。负数表示失败;0 表示成功,但后续获取可能失败;正数表示成功,且后续获取可能成功。
  4. boolean tryReleaseShared(int arg)共享式释放资源。如果释放后允许后续等待节点获取资源,则返回 true,否则返回 false。
  5. boolean isHeldExclusively(): 当前同步器是否被当前线程独占持有。
重要的模板方法 (Public Final)

这些方法是提供给使用者(或子类)的 API,逻辑已经由 AQS 实现好,通常是 final 的。

获取资源

  1. acquire(int arg)独占式获取资源,忽略中断。这是 lock() 的底层实现。acquire -> tryAcquire -> (如果失败) addWaiter -> acquireQueued
  2. acquireInterruptibly(int arg): 同上,但响应中断。
  3. acquireShared(int arg)共享式获取资源,忽略中断。
  4. acquireSharedInterruptibly(int arg): 同上,但响应中断。

释放资源

        release(int arg)独占式释放资源。release -> tryRelease -> unparkSuccessor

        releaseShared(int arg)共享式释放资源。

其他

        boolean hasQueuedThreads(): 是否有线程在等待。

        Collection<Thread> getQueuedThreads(): 获取等待线程集合。


四、工作流程详解 (以独占模式为例)

获取锁 (acquire(int arg))
  1. tryAcquire: 调用子类实现的 tryAcquire(arg) 方法尝试直接获取资源。

  2. 成功: 如果成功,流程结束,线程继续执行。

  3. 失败: 如果失败,AQS 会将当前线程包装成一个独占模式的 Node 节点,并通过 addWaiter(Node.EXCLUSIVE) 方法将其添加到同步队列的尾部

  4. acquireQueued: 然后调用 acquireQueued 方法,让节点以自旋(循环) 的方式尝试获取资源。

    • 在自旋中,只有当前节点的前驱节点是头节点 (head) 时,它才有资格再次尝试调用 tryAcquire 获取资源。

    • 如果获取成功,则将当前节点设置为新的头节点,原头节点出队。

    • 如果获取失败,则判断是否需要阻塞当前线程(通过 shouldParkAfterFailedAcquire 方法检查前驱节点的状态)。如果需要,则调用 parkAndCheckInterrupt() 方法,底层使用 LockSupport.park(this) 阻塞当前线程。

  5. 阻塞与唤醒: 线程被阻塞,等待被唤醒。当持有锁的线程调用 release 释放资源时,会唤醒头节点的后继节点。被唤醒的线程会从 parkAndCheckInterrupt() 中返回,并再次进行自旋尝试获取锁。

释放锁 (release(int arg))
  1. tryRelease: 调用子类实现的 tryRelease(arg) 方法尝试释放资源。

  2. 成功: 如果释放成功(例如,state 变为 0),则获取当前头节点 h

  3. unparkSuccessor: 如果头节点存在且 waitStatus != 0(表示它有后继节点需要唤醒),则调用 unparkSuccessor(h) 方法,找到头节点后面第一个未被取消的节点,并使用 LockSupport.unpark(s.thread) 唤醒该节点对应的线程。


五、实战:看 ReentrantLock 如何基于 AQS 实现

ReentrantLock 内部有一个同步器 Sync,它继承自 AQS。

state 的含义: 锁的重入次数。

tryAcquire:

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取当前状态if (c == 0) { // 锁空闲if (!hasQueuedPredecessors() && // 公平锁才会检查是否有前驱节点在等待compareAndSetState(0, acquires)) { // CAS 抢锁setExclusiveOwnerThread(current); // 成功,设置当前线程为锁持有者return true;}}else if (current == getExclusiveOwnerThread()) { // 锁已被持有,且是当前线程重入int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc); // 直接修改 state,无需 CAS,因为就是当前线程return true;}return false; // 获取失败
}

tryRelease:


protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 重入次数减为0,才真正释放free = true;setExclusiveOwnerThread(null);}setState(c); // 更新 statereturn free;
}
 

当我们调用 lock.lock() 时,底层就是调用了 sync.acquire(1),进而触发了上述流程。


总结

特性描述
核心一个状态变量 state + 一个 FIFO 线程等待队列
设计模式模板方法模式。子类实现 tryAcquiretryRelease 等方法来定义资源获取和释放的规则
关键操作通过 CAS 操作来原子性地修改 state
线程阻塞/唤醒使用 LockSupport.park() 和 LockSupport.unpark()
优点极大地简化了同步器的开发,将复杂的线程排队、阻塞、唤醒等底层操作封装好,开发者只需关注对 state 的管理

理解 AQS,就等于拿到了理解 Java 并发包中大部分同步工具实现原理的钥匙。它是 Java 并发大师 Doug Lea 的杰作,是 Java 并发编程中一座重要的里程碑。


文章转载自:

http://aM3aaJA8.Ldwxj.cn
http://tF7fa6Hv.Ldwxj.cn
http://ixNHOX0y.Ldwxj.cn
http://Loq5KFee.Ldwxj.cn
http://WqJBTz2W.Ldwxj.cn
http://WkPhMUNc.Ldwxj.cn
http://2Ef01o7V.Ldwxj.cn
http://2BBrVOrB.Ldwxj.cn
http://mY32tYzs.Ldwxj.cn
http://NLOnbz4o.Ldwxj.cn
http://n3ZBznGe.Ldwxj.cn
http://oHWlhDVx.Ldwxj.cn
http://UvOvvBuj.Ldwxj.cn
http://HFgzA6ck.Ldwxj.cn
http://hzGqkkfn.Ldwxj.cn
http://B6I6H6OY.Ldwxj.cn
http://R4l4Wf8g.Ldwxj.cn
http://jgjLrgal.Ldwxj.cn
http://VRfqlIog.Ldwxj.cn
http://p1j7jPhv.Ldwxj.cn
http://uP1IKSub.Ldwxj.cn
http://tuPgxMnF.Ldwxj.cn
http://dJdOJzcX.Ldwxj.cn
http://1t4XxmZi.Ldwxj.cn
http://pSmAs3a4.Ldwxj.cn
http://I5uEFAH0.Ldwxj.cn
http://eM1uykj7.Ldwxj.cn
http://sHRKYiwo.Ldwxj.cn
http://kbL4PTdi.Ldwxj.cn
http://eIoTuHj5.Ldwxj.cn
http://www.dtcms.com/a/383775.html

相关文章:

  • 实验-基本ACL
  • 开始 ComfyUI 的 AI 绘图之旅-SDXL文生图和图生图(全网首发,官网都没有更新)(十四)
  • Java可用打印数组方法5中+常用变量转字符串方法
  • ssh远程连接服务器到vscode上“连接失败”
  • SpringBoot -原理篇
  • 设计模式——结构型模式
  • I.MX6ULL时钟(clock)与定时器(EPITGPT)
  • STM32_06_Systick定时器
  • 用 Java 学会 Protocol Buffers从 0 到 1 的完整实战
  • 237.删除链表中的节点
  • 【Vue2手录14】导航守卫
  • Qt如何读写xml文件,几种方式对比,读写xml的Demo工程
  • 子网划分专项训练-1,eNSP实验,vlan/dhcp,IP规划
  • 云原生改造实战:Spring Boot 应用的 Kubernetes 迁移全指南
  • 看门狗的驱动原理
  • [论文阅读] 人工智能 + 软件工程 | 大语言模型驱动的多来源漏洞影响库识别研究解析
  • 【前缀和+哈希表】P3131 [USACO16JAN] Subsequences Summing to Sevens S
  • 05.【Linux系统编程】进程(进程概念、进程状态(注意僵尸和孤儿)、进程优先级、进程切换和调度)
  • 【从零开始java学习|小结】记录学习和编程中的问题
  • 图像拼接案例,抠图案例
  • 分层解耦讲解
  • 安装Hadoop中遇到的一些问题和解决
  • 音视频-色域
  • 返利软件的分布式缓存架构:Redis集群在高并发场景下的优化策略
  • 如何让知识上传与查询更便捷
  • set/multiset容器
  • 区块链:搭建简单Fabric网络并调用智能合约
  • Keepalived的详细实操安装流程及其配置文件选项的详解
  • windows下,podman迁移镜像文件位置
  • 技能补全之正则表达式