🚀 优质资源分享 🚀
| 学习路线指引(点击解锁) | 知识定位 | 人群定位 |
|---|---|---|
| 🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
| 💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
一、前言
AQS 是一个同步框架,关于同步在操作系统(一)—— 进程同步 中对进程同步做了些概念性的介绍,我们了解到进程(线程同理,本文基于 JVM 讲解,故下文只称线程)同步的工具有很多:Mutex、Semaphore、Monitor。但是Mutex 和 Semaphore 作为低级通信存在不少缺点,Monitor 机制解决了上面部分缺憾,但是仍然存在问题,AQS 的出现很好的解决了这些问题。
二、其他同步工具的缺点
Mutex
- Mutex 只有两种状态,锁定和非锁定,无法表示临界区的可用资源数量(计数信号量可解决)。
- 使用复杂,使用不当易造成死锁
Semaphore
- Mutex 只允许一个线程访问临界资源, Semaphore 允许一定数量的线程访问共享资源。但是 Semaphore 中没有 Owner,无法知道当前获取锁的线程是谁。
- 使用复杂,使用不当易造成死锁,P V 操作需要配对使用,分散在代码各处增大了编码难度。
Monitor
Monitor 解决了上述几个问题,但在 HotSpot 中底层是基于 Mutex 做的线程同步,在 1.6 之前且还没有进行优化,每次锁竞争都需要经历两次上下文切换严重影响性能。第二个问题是 HotSpot 实现的是精简的 Mesa 语义,不支持多个条件变量。
三、AQS 概述
什么是 AQS ?
AQS 即一个类,java.util.concurrent.AbstractQueuedSynchronized.Class,这个类作为一个构造同步器的框架。AQS 本身并不提供 API 给程序员用于直接的同步控制,它是一个抽象类,通过实现它的抽象方法来构建同步工具,如 ReentrantLock、CountDownLatch 等。也就是说它不是一个面向业务开发使用的工具,是构建同步器所复用的一套机制抽取出来形成的框架,这个框架我们自己也可以通过实现它的抽象方法来构建自己的同步器。
AQS 做了什么事?
其实通过观察同步器的各种实现都是相似的,我们会发现锁的实现通常需要以下三要素:
- 状态管理
- 加锁解锁操作
- 线程等待
状态管理在信号量中是整型值,在 Mutex 中就是 Owner,在 Synchronized (Monitor) 中也是 Owner,这些机制都有对应的操作来加锁和解锁,通常加锁/解锁操作也对应着线程的阻塞(等待)/唤醒,这些线程没有获取到锁后需要等待,有的实现是自旋,有的实现是挂起。不论是 Synchronized 还是 AQS 都有等待队列供未获取到锁的线程进入,直到锁释放。
同理,AQS 所做的主要的三件事也就是上面三件,只是每一项的实现细节可能不同,支持的功能更广泛。
- 状态的原子性管理
- 加锁/解锁操作
- 队列管理(线程阻塞入队/唤醒出队)
下文会先列出 AQS 的设计,也就是实现了哪些特性,接下来就会通过看源码和图示来了解这些设计的具体实现。
AQS 设计
- 阻塞和非阻塞
- 可选超时设置,在超时后放弃等待锁
- 锁可中断
- 独占和共享模式
独占模式即同一时刻只能有一个线程可以通过阻塞点,共享模式下可以同时有多个线程在执行。
以上都是 AQS 需要支持的功能,基于模板模式的设计,AQS 提供了以下方法供子类继承重新实现:

- tryAcquire()/tryRelease() 为独占模式下的加锁解锁操作
- tryAcquireShared()/tryReleaseShared() 为共享模式下的加锁解锁操作
- isHeldExclusively() 表明锁是否为独占模式,即当前线程是否独占资源。
四、AQS 原理
原理概括
关于同步器的实现思路,首先需要有一个状态来标明锁是否可用,在 AQS 的实现中,其维护了一个变量 state,这个变量使用 volatile 关键字进行标识,保证其在线程之间的可见性。加锁解锁操作简化来看就是只需要把这个状态更改,且标明当前线程占有锁。在独占模式下,加锁操作必须互斥,也就是在同一时刻只能有一个线程加锁成功,AQS 使用 CAS 原子指令来保证 State 的互斥访问。在一个线程成功加锁(改变锁的状态 State 的值,该值具体如何改变取决于同步工具如何实现)之后,其他线程尝试 CAS 则会加锁失败,那么加锁失败的线程该如何呢?这些线程可以自旋等待或者阻塞,AQS 提供 CLH 队列将这些阻塞的线程管理起来,CLH 是一个先进先出的队列,加锁失败的线程会被进入队列阻塞。因此加锁和解锁操作并不仅仅是简单的修改锁状态,在这之后还需要维护队列。AQS 的主要原理也就是围绕着队列进行入,加锁解锁功能由继承 AQS 的同步工具实现,在调用加锁操作之后,AQS 来维护线程入队,并且将线程阻塞,在调用解锁操作后,AQS 将队列中的线程按规则出队并且唤醒。
Node 设计
static final class Node {
static final Node SHARED = new Node(); // 共享模式下的节点
static final Node EXCLUSIVE = null; // 独占模式下的节点
static final int CANCELLED = 1; // 被取消了的状态
static final int SIGNAL = -1; // 处于park() 状态等待被unpark()唤醒的状态
static final int CONDITION = -2; // 处于等待 condition 的状态
static final int PROPAGATE = -3; // 共享模式下需要继续传播的状态
volatile int waitStatus; // 当前节点的状态
volatile N

本文深入探讨了AQS(AbstractQueuedSynchronized)的原理和设计,包括其作为同步器框架的角色,如何解决其他同步工具的缺点,以及其独占和共享模式下的加锁解锁机制。通过源码分析,详细阐述了AQS如何维护线程状态,使用CLH队列管理阻塞线程,并通过Node设计实现线程入队和出队。文章还介绍了在加锁失败后线程如何通过自旋和阻塞等待,并在解锁时如何唤醒等待线程。
最低0.47元/天 解锁文章
—— 源码解析 AQS 原理&spm=1001.2101.3001.5002&articleId=126032496&d=1&t=3&u=397769e264dc4ee09c68ef15658ac3bc)
503

被折叠的 条评论
为什么被折叠?



