AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
首先是AQS的父类AbstractOwnableSynchronizer,这个类很简单,就一个成员变量以及它的getter/setter方法,
exclusiveOwnerThread用来标记当前同步器被哪个线程占用。
private transient Thread exclusiveOwnerThread;// 独占同步模式的当前所有者。
Node是一个FIFO的队列上的节点类,该队列是一个双向链表结构,Node使用成员变量prev和next来指向上一个和下一个Node节点。
Node的成员如下:
static final Node SHARED = new Node(); 表明Node正处于共享模式下等待。
static final Node EXCLUSIVE = null; 表明Node正处于独占模式下等待。
volatile Node prev; 前置Node节点。
volatile Node next; 后置Node节点。
volatile Thread thread; 该Node节点中包含的线程。
volatile int waitStatus; 当前节点的状态。他的值包括如下:
static final int CANCELLED = 1; 节点被取消。
static final int SIGNAL = -1; 后置节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
static final int CONDITION = -2; 当前节点进入等待队列中
static final int PROPAGATE = -3; 表示下一次共享式同步状态获取将会无条件传播下去
static final int INITIAL = 0; 初始状态。
Node nextWaiter; 等待队列中的下一个节点,或者指向特定值上面的SHARED成员。
final boolean isShared() {return nextWaiter == SHARED;}; 返回true表示该Node处于共享模式下等待。印证了上面的特定值SHARED的含义。
final Node predecessor(); 返回前置Node节点。
private transient volatile Node head; 队列头部节点。
private transient volatile Node tail; 队列尾部节点。
private volatile int state; 当前同步状态。
我们一般使用ReetrantLock的代码如下:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
public ReentrantLock() {
sync = new NonfairSync();
}
可以看到,空参的ReentrantLock默认是创建的非公平锁。职于公平锁和非公平锁的区别,我们会在后面分析完源码后得出结论。
sync是ReentrantLock的内部类,它继承自AQS,从这里可以看到ReentrantLock其实就是使用AQS来实现锁机制的。
Sync有两个实现类,FairSync和NonfairSync,根据ReentrantLock的构造参数来创建不同的Sync实现类。
这里我们先以默认实现非公平锁来分析。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
该方法首先通过CAS设置当前AQS的state为1,如果设置成功,会将当前线程设置到AQS的独占模式所有者的成员中,也就是表明当前线程获取到了锁。
如果设置失败继续调用AQS的acquire(1)。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()是一个抽象方法,需要AQS的子类来实现。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果AQS当前status为0,则尝试通过CAS获取锁。如果state不为0,判断当前独占模式所有者是不是当前线程,如果是,重新设置state,一般就是加1,这也就是我们常说的可重入锁,同一个线程可以多次获取同一个锁。 如果当前独占不是当前线程,则返回false,表明尝试获取锁失败。
当tryAcquire()尝试获取锁失败,会继续调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
private Node addWaiter(Node mode) {
//创建一个nextWaiter为Node.EXCLUSIVE,
//thread为当前线程的Node节点。
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//当AQS的尾部节点不为空,尝试通过CAS将上面的新节点设为队尾。
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//当AQS的尾部节点为空,或者上面通过CAS设置队尾失败的话,调用enq入队。
enq(node);
return node;
}
AQS传入一个独占模式的Node.EXCLUSIVE给addWaiter()方法。我们再来看下enq()方法。
//enq()方法通过CAS自旋来入队。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//当AQS的队尾为null,
//创建一个新的空的Node来充当队首和队尾。
if (compareAndSetHead(new Node()))
tail = head;
} else {
//通过CAS将Node入队。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
通过上面可以看出,addWaiter()方法的作用就是将新的Node节点入队到AQS的队尾,如果是空队列,会创建一个空的Node来充当队首。
上面addWaiter()方法将新的线程包装到Node中,并将该Node通过CAS加入的AQS的队尾并返回传递给acquireQueued()方法。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果新建的Node的prev前置节点是AQS的队首,
//那么再次尝试获取锁。
if (p == head && tryAcquire(arg)) {
//如果获取成功,将该node设置成队首,
//同时将原队首的next置为空,从而将原队首出队。
//设置头的时候,会将node的thread和prev都设为空。
//所以队首一般都相当于一个虚拟的Node。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//否则会挂起当前线程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued()方法会首先判断新建的Node的前置节点是否为AQS的队首,如果是,再次尝试tryAcquire()获取锁,如果成功,将当前新的Node设置为队首,并将原队首出队。
否则的话,会继续调用shouldParkAfterFailedAcquire()尝试挂起当前线程。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire方法的作用通过源码可以知道,将新入队的Node的前置节点的waitStatus置为Node.SIGNAL。同时需要注意的是,如果前置节点的状态是大于0,一般就是CANCELLED=1,说明前置节点被取消了。它会遍历该前置节点的前置节点,直到找到状态不是CANCELLED的,并将该不CANCELLED的Node设置为当前Node的前置节点。从而将中间那些CANCELLED的Node全部出队。
只有shouldParkAfterFailedAcquire()中pred本来就是SIGNAL状态的时候才会返回true,其他情况都是false。当返回true的时候继续调用parkAndCheckInterrupt(),使用LockSupport.park(this)将当前线程挂起。当shouldParkAfterFailedAcquire()返回false的时候,不会挂起线程,继续for循环自旋直到线程挂起或者Node的前置节点成为head并且tryAcquire成功。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
parkAndCheckInterrupt()使用LockSupport.park()实现线程挂起。
如此,整个获取锁的过程就分析完了。ReentrantLock的非公平锁在调用lock()的时候就尝试获取锁,当获取失败就会加入到AQS的队列中被挂起,等待unpark()。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
如果当前线程不是AQS独占模式的所有者线程,则抛出异常,否则将AQS的state-1。当state=0的设置释放锁,设置独占所有者为null。因为ReentrantLock是可重入锁,前面在lock的时候,如果相同线程多次加锁,会将state多次+1。所以在这里释放的时候也需要相同次数的unlock,将state减到0。
当tryRelease()将state减为0,返回true。AQS的release()方法会继续调用unparkSuccessor(),启动队列中下一个节点的线程。
lock.lockInterruptibly()。和lock()方法大致相同,只是多了可以抛出InterruptedException异常。
公平锁就是在创建ReentrantLock的时候传入true。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的lock()方法直接调用的AQS的acquire()方法,所以和非公平锁就需要实现的抽象方法tryAcquire()不一样。下面看下公平锁的tryAcquire()。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
hasQueuedPredecessors()判断队列中是否有除了head以外的有效Node节点。
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从上面可以看到公平锁和非公平锁的区别:
ReentrantLock在lock的时候非公平锁会尝试竞争获取锁,其他的和公平锁基本一样,如果获取锁失败,他们就会入队到AQS的FIFO的队列当中,当前置节点释放锁后,非公平锁的后续节点会和刚刚创建还未入队的线程争抢锁,公平锁则直接按照队列顺序向后释放线程。
通过AQS的state是否等于0判断当前AQS同步器是否被占用,相同线程重复获取锁,state会依次逐渐+1,同理释放锁的时候依次-1,直到state再次等于0,表示锁被释放。
AQS通过LockSupport.park()和LockSupport.unpark()来挂起和释放线程,使用起来比较方便。