1. 什么是AQS
AQS的全称是AbstractQueuedSynchronizer,即抽象队列同步器,这个类在java.uitl.concurrent.locks包下面。
AQS就是一个抽象类,主要用来构建锁和同步器。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}
AQS为构建锁和同步器提供了一些通用功能的实现,因此使用AQS能简单且高效地构建出应用广泛的大量的同步器。
比如常用的ReentrantLock,Semaphore,其他类似ReentrantReadWriteLock,SynchronousQueue等等皆是基于 AQS 的
2. AQS原理
2.1 AQS核心思想
AQS核心思想在于:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的共享资源被占用,那么就需要一套阻塞等待以及被唤醒时锁分配的机制,这个机制是基于CLH锁实现的。
CLH锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列不存在队列实例,仅存在结点之间的关系),暂时获取不到锁的线程将被加入到队列中。AQS将每条请求共享资源的线程封装为一个CLH队列锁的一个结点(Node)来实现锁的分配。在CLH队列锁中,一个节点表示一个线程,它保存着线程的引用、当前节点在队列中状态、前驱节点、后继节点。
Node结点的源码如下所示:
abstract static class Node {
volatile Node prev; // 前驱结点
volatile Node next; // 后继结点
Thread waiter; // 线程引用
volatile int status; // 线程的状态
// methods for atomic operations final boolean casPrev(Node c, Node v) { // for cleanQueue
return U.weakCompareAndSetReference(this, PREV, c, v);
}
final boolean casNext(Node c, Node v) { // for cleanQueue
return U.weakCompareAndSetReference(this, NEXT, c, v);
}
final int getAndUnsetStatus(int v) { // for signalling
return U.getAndBitwiseAndInt(this, STATUS, ~v);
}
final void setPrevRelaxed(Node p) { // for off-queue assignment
U.putReference(this, PREV, p);
}
final void setStatusRelaxed(int s) { // for off-queue assignment
U.putInt(this, STATUS, s);
}
final void clearStatus() { // for reducing unneeded signals
U.putIntOpaque(this, STATUS, 0);
}
private static final long STATUS = U.objectFieldOffset(Node.class, "status");
private static final long NEXT = U.objectFieldOffset(Node.class, "next");
private static final long PREV = U.objectFieldOffset(Node.class, "prev");
}
CLH队列结构如下图所示:
AQS(AbstractQueuedSynchronizer
)的核心原理图:
AQS使用成员变量state,表示同步状态,通过内置的FIFO线程等待/等待队列来完成获取资源线程的排队工作。
AQS抽象类中内置了head和tail结点。
/**
* Head of the wait queue, lazily initialized. */
private transient volatile Node head;
/**
* Tail of the wait queue. After initialization, modified only via casTail. */
private transient volatile Node tail;
state变量由volatile修饰,用于展示当前临界资源的获取锁的情况。
private volatile int state;
另外,状态信息state可以通过protected类型的getState()、setState()和compareAndSetState()进行操作。并且,这几个方法都是通过final修饰的,在子类无法被重写。
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
以可重入的互斥锁ReentrantLock为例,它的内部维护了一个state变量,用来表示锁的占用状态。
- state的初始值为0,表示锁处于未锁定状态。
- 当线程A调用了lock()方法时,会尝试通过tryAcquire()方法独占锁并让state的值+1。
- 如果成功了,那么线程A就获取到了锁,如果失败了,那么线程A就会被加入到一个等待队列中(CLH队列)直到其他线程释放该锁。
- 假设线程A获取锁成功了,释放锁之前,A线程自己是可以重复获取此锁的(state会累加)。
- 这是可重入的表现:一个线程可以多次获取同一个锁而不会被阻塞,但是,这也意味着,一个线程必须释放全部次数的锁,
才可以让state变为0,就是让锁恢复到未锁定的状态。只有这样,其他等待的线程才可以去获取锁。
线程 A 尝试获取锁的过程如下图所示:
倒计时器CountDownLatch为例子:
- 任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。
- 这N个子线程开始执行任务,每执行完一个子线程,就调用一次countDown()方法。
- 该方法会尝试使用CAS操作,让state的值减少1。
- 当所有的子线程执行完毕后,CountDownLatch会调用unpark()方法,唤醒主线程。
- 这时主线程就可以从await()方法(CountDownLatch中的await()方法而非AQS中的)返回,继续执行后续的操作。
2.2 AQS资源共享方式
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore和CountDownLatch)
一般来说,自定义同步器的共享方式要么是独占,要么是共享,他们只需要实现tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock
。
2.3 自定义同步器
同步器的设计是基于模板方法模式的,如果需要自定义同步器,一般的方式如下:
- 使用者继承
AbstractQueuedSynchronizer
并重写指定的方法。 - 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
AQS使用了模板方法模式,自定义同步器时需要重写下面的几个AQS提供的钩子方法:
//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryReleaseShared(int)
//该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively()