前言
在Java面试的时候,多线程相关的知识是躲不掉的,肯定会被问。我就被问到了AQS的知识,就直接了当的问,AQS知道是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。当时自己确实没有讲好,所以这次来总结一下这个知识点。
什么是AQS
AQS全称是AbstractQueuedSynchronizer,形如其名,抽象队列同步器。 AQS定义了两种资源共享模式:
-
独占式,每次只能有一个线程持有锁,例如ReentrantLock实现的就是独占式的锁资源。
-
共享式,允许多个线程同时获取锁,并发访问共享资源,ReentrantWriteLock和CountDownLatch等就是实现的这种模式。
它维护了一个volatile的state变量和一个FIFO(先进先出)的队列。 其中state变量代表的是竞争资源标识,而队列代表的是竞争资源失败的线程排队时存放的容器。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
...
/**
* The synchronization state.
*/
private volatile int state;
/**
* Wait queue node class.
**/
static final class Node {
...
}
...
}
AQS中提供了操作state的方法:
-
getState();
-
setState();
-
compareSetState();
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
因为AbstractQueuedSynchronizer是一个抽象类,他采用模板方法的设计模式,规定了独占和共享模式需要实现的方法,并且将一些通用的功能已经进行了实现,所以不同模式的使用方式,只需要自己定义好实现共享资源的获取与释放即可,至于具体线程在等待队列中的维护(获取资源入队列、唤醒出队列等),AQS已经实现好了。
所以根据共享资源的模式一般实现的方法有如下几个:
-
isHeldExclusively();// 是否为独占模式;但是只有使用到了Condition的,才需要去实现它。例如:ReentrantLock。
-
boolean tryAcquire(int arg); // 独占模式;尝试获取资源,成功返回true,失败返回false。
-
boolean tryRelease(int arg) ; // 独占模式;尝试释放资源,成功返回true,失败返回false。
-
int tryAcquireShared(int arg); // 共享模式;尝试获取资源,负数表示失败;0表示成功,但是没有剩余可用资源了;正数表示成功,且有剩余可用资源。
-
boolean tryReleaseShared(int arg) ; // 共享模式;尝试释放资源,若释放资源后允许唤醒后续等待节点返回true,否则返回false。
上面的这几个方法在AbstractQueuedSynchronizer这个抽象类中,都没有被定义为abstract的,说明这些方法都是可以按需实现的,共享模式下可以只实现共享模式的方法(例如CountDownLatch),独占模式下可以只实现独占模式的方法(例如ReentrantLock),也支持两种都实现,两种模式都使用(例如ReentrantReadWriteLock)。
AQS源码分析
我们先简单介绍AQS的两种模式的实现类的代表ReentrantLock(独占模式)和CountDownLatch(共享模式),是如何来共享资源的一个过程,然后再详细通过AQS的源码来分析整个实现过程。
-
ReentrantLock在初始化的时候state=0,表示资源未被锁定。当A线程执行lock()方法时,会调用tryAcquire()方法,将AQS中队列的模式设置为独占,并将独占线程设置为线程A,以及将state+1。 这样在线程A没有释放锁前,其他线程来竞争锁,调用tryAcquire()方法时都会失败,然后竞争锁失败的线程就会进入到队列中。当线程A调用执行unlock()方法将state=0后,其他线程才有机会获取锁(注意ReentrantLock是可重入的,同一线程多次获取锁时state值会进行垒加的,在释放锁时也要释放相应的次数才算完全释放了锁)。
-
CountDownLatch会将任务分成N个子线程去执行,state的初始值也是N(state与子线程数量一致)。N个子线程是并行执行的,每个子线程执行完成后countDown()一次,state会通过CAS方式减1。直到所有子线程执行完成后(state=0),会通过unpark()方法唤醒主线程,然后主线程就会从await()方法返回,继续后续操作。
独占模式分析
在AbstractQueuedSynchronizer的类里面有一个静态内部类Node。它代表的是队列中的每一个节点。 其中Node节点有如下几个属性:
// 节点的状态
volatile int waitStatus;
// 当前节点的前一个节点
volatile Node prev;
// 当前节点的后一个节点
volatile Node next;
// 当前节点中所包含的线程对象
volatile Thread thread;
// 等待队列中的下一个节点
Node nextWaiter;
每个属性代表的什么,已经写在代码注释中了。其中Node类中还有几个常量,代表了几个节点的状态(waitStatus)值。
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
首先节点的状态值waitStatus默认是0,然后下面几个常量有自己具体的含义。 CANCELLED = 1; 代表的是当前节点从同步队列中取消,当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。 SIGNAL = -1; 代表后继节点处于等待状态。后继结点入队时,会将前继结点的状态更