AQS(AbstractQueuedSynchronizer) 是 Java 并发包(java.util.concurrent)中的一个抽象类,它是用来构建锁(Lock)和同步器(Synchronizer)框架的基础。AQS 提供了一套通用的机制,用于实现依赖于队列的并发控制结构,比如常见的锁、信号量、倒计时器等。AQS 是许多并发工具的核心,如 ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier 等,都是基于 AQS 来实现的。
AQS 的基本原理
AQS 的设计核心是一个基于 FIFO(First-In-First-Out)等待队列的同步器。它通过一个volatile 变量 state 来表示同步状态,并使用一种模板化的方法,让子类去实现具体的同步语义。
AQS 内部维护了一个双向链表,表示多个线程在争抢资源时的等待队列。线程获取资源失败时,会进入队列进行等待,直到资源可用被唤醒。
AQS 的主要功能
AQS 提供了两种主要的锁获取模式:
- 独占模式(Exclusive Mode) :
在独占模式下,只有一个线程可以成功获取锁,其他线程必须等待。例如,ReentrantLock使用的就是独占模式。
-
- 如果当前线程可以获取锁(资源状态),则成功获取;
- 如果不能,则线程会被挂起并加入等待队列,直到资源可用。
- 共享模式(Shared Mode) :
在共享模式下,多个线程可以同时获取资源。常见的例子是CountDownLatch,多个线程可以共享某些资源。
-
- 一次可以有多个线程成功获取资源。
AQS 的关键组件
- 同步状态(state) :
AQS 使用state变量来表示资源的状态,比如锁是否被持有,或计数器的值。state是一个int类型的 volatile 变量,AQS 提供了一些原子操作来操作它(如getState()、setState()和compareAndSetState())。 - 等待队列(Wait Queue) :
AQS 通过内部的 FIFO 队列来管理线程的等待。当一个线程无法获取资源时,它会被封装成一个Node对象,加入到队列中,等待资源可用时再被唤醒。 - 独占和共享模式:
-
- 在独占模式下,一个线程成功获取资源后,其他线程必须等待。
- 在共享模式下,多个线程可以同时成功获取资源。
- 锁的获取和释放:
AQS 定义了acquire()和release()方法,分别用于获取和释放资源。在获取资源失败时,线程会被阻塞并进入等待队列,而当资源被释放时,会从队列中唤醒等待的线程。
AQS 的主要方法
- acquire(int arg) :独占模式下,线程试图获取锁。如果失败,线程进入等待队列。
- release(int arg) :独占模式下,线程释放锁并唤醒等待队列中的线程。
- acquireShared(int arg) :共享模式下,线程获取共享锁,成功时返回一个非负数。
- releaseShared(int arg) :共享模式下,线程释放锁并唤醒等待线程。
- tryAcquire(int arg) :独占模式下,尝试获取锁,需子类实现。
- tryRelease(int arg) :独占模式下,尝试释放锁,需子类实现。
- tryAcquireShared(int arg) :共享模式下,尝试获取共享锁,需子类实现。
- tryReleaseShared(int arg) :共享模式下,尝试释放共享锁,需子类实现。
AQS 的使用场景
ReentrantLock:可重入锁,基于 AQS 实现的独占模式。Semaphore:信号量,基于 AQS 实现的共享模式。CountDownLatch:基于 AQS 实现的共享模式,线程等待计数器归零。CyclicBarrier:基于 AQS 实现的可重复屏障机制。ReentrantReadWriteLock:读写锁,其中读锁是共享模式,写锁是独占模式。
AQS 工作流程
- 当线程尝试获取锁时,调用
acquire()方法,该方法内部调用tryAcquire(),如果返回true,表示获取成功,否则线程会被加入等待队列。 - 如果锁被释放,调用
release()方法,该方法会更新state并唤醒队列中的等待线程。 - 等待的线程在合适的时候被唤醒,重新尝试获取锁。
最后总结
AQS 提供了一种通用的同步机制,使得开发人员能够方便地实现自定义的锁和同步器。通过将复杂的同步控制逻辑(如锁的竞争和管理)封装在 AQS 内部,开发者可以专注于实现高层次的业务逻辑,从而减少开发难度和提升代码质量。