AbstractQueuedSynchronizer——JUC包的基石(上)
AQS介绍
- AbstractQueuedSynchronizer抽象队列同步器,是JUC包中的一个抽象类,提供java API层面的同步控制实现;
- AQS是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),是JUC并发包中的核心基础组件。
- AQS的主要使用方式是继承,AQS的核心方法使用设计模式模板方法进行设计,于是子类通过继承AQS同步器并实现它的抽象方法即可管理同步状态。
- AQS使用CAS交换状态保证同一时刻只有一个线程获取到资源,其余线程进入CLH同步队列,进行重试、重试失败就阻塞挂起。CAS可以降低上下文切换的开销,而线程阻塞挂起则会释放CPU,从而提高吞吐量,提高系统资源使用。
- Node节点关键属性
5.1 volatile int waitStatus;
- CANCELLED =1 表明当前线程处于取消状态
- SIGNAL = -1 表明当前节点释放状态(锁)时,后继节点需要被唤醒
- CONDITION = -2 表明当前线程处于等待条件的状态,如ReentrantLock类的await方法;
- PROPAGATE = -3 indicate the next acquireShared should unconditionally propagate: 表示下一次共享式同步状态获取将会无条件地传播下去
5.2 volatile Node prev; volatile Node next;说明CLH同步队列是一个双端队列;
5.3 volatile Thread thread; 当前节点的线程;
acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//前继节点是头节点并且获取同步状态成功
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//判断到当前节点的前节点状态为SIGNAL时,就会返回true,此时当前线程就会进入阻塞挂起状态
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//第一次调用shouldParkAfterFailedAcquire方法会先将当前节点的前节点(变量p)状态设置为SIGNAL,表明当前节点的前节点释放状态(锁)时,后继节点需要被唤醒。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回值false,当前线程并不会阻塞,会在下一次for循环中重复调用shouldParkAfterFailedAcquire方法进行判断
//如果下一次for循环中成功获取到同步状态那么也就不用阻塞挂起线程了,这里有一点自旋锁的设计;
return false;
}
- acquire方法是设计模式模板方法的体现,子类通过实现tryAcquir方法控制自身的状态同步逻辑。
- 多数tryAcquire方法都是通过cas技术交换state状态进行的。并不是当state>0时表示已经获取了锁,当state=0时表示释放了锁(比如CountDownLatch)。而是主要看子类实现的tryAcquire方法返回true/false的逻辑。返回false进入acquireQueued方法。
- addWaiter方法和acquireQueued方法通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态(锁)失败时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程。
release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//头节点不为空并且状态不等于0(SIGNAL状态为-1) 那么就唤醒头节点的后一个节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- release方法同样也是模板方法的体现,子类通过实现tryRelease方法控制自身的状态释放逻辑。同样大部分子类也是使用cas技术进行操作;
- unparkSuccessor方法在tryRelease方法返回true时唤醒CLH同步队列头节点的后继节点(首节点);
- 被唤醒的节点会继续在acquireQueued方法的for循环中重试执行tryAcquire获取同步状态(锁);
ReentrantLock
概述
- 有三个内部类Sync、NonfairSync、FairSync。其中Sync是抽象内部类,继承了AbstractQueuedSynchronizer同步器。ReentrantLock持有一个Sync属性对象,无参构造器中Sync属性实现的是NonfairSync。
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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;
}
- NonfairSync重写的tryAcquire方法主要逻辑在nonfairTryAcquire中。可以看到都是通过CAS技术来进行原子操作state
- CAS操作成功或者时当前线程的重入获取那么就会返回true。如果失败那么就返回false,此时就会进入acquire方法中的acquireQueued方法调用,进行线程节点排队、阻塞;
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
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;
}
}
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());
}
- 公平锁的具体实现hasQueuedPredecessors()——判断CLH同步队列是否存在非当前线程的节点。如果存在那么必须让CLH同步队列的节点先获得获取状态(锁)的资格;
CountDownLatch
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//可以看到CountDownLatch#Sync重写的tryAcquireShared方法中,并不是getState大于0就是成功获取同步状态(锁),具体要看tryAcquireShared方法的逻辑。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
- CountDownLatch含有Sync这个静态内部类,继承了AbstractQueuedSynchronizer队列同步器;
- 用给定的计数初始化CountDownLatch。由于调用了countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。
- 程序进行countDown调用,当计数到达零时,此时会释放所有等待的线程,具体的方法栈调用链路是CountDownLatch#countDown——AQS#releaseShared——AQS#doReleaseShared(这时会唤醒阻塞在AQS#doAcquireSharedInterruptibly方法的线程)——AQS#setHeadAndPropagate(传播唤醒CLH同步队列的所有节点)
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//计数为0时,doReleaseShared方法会唤醒阻塞在AQS#doAcquireSharedInterruptibly方法的线程
doReleaseShared();
return true;
}
return false;
}
//await方法调用阻塞、唤醒、传播唤醒调用链
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//设置头节点 并传播唤醒CLH同步队列中的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//更新设置头节点
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//doReleaseShared方法会唤醒阻塞在AQS#doAcquireSharedInterruptibly方法的线程
//而doAcquireSharedInterruptibly方法会调用setHeadAndPropagate方法以达到传播唤醒所有节点的效果
doReleaseShared();
}
}
Semaphore
简单看一下Semaphore的acquire方法逻辑
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//当tryAcquireShared方法返回的int值小于0时,说明信号量已经用完,那么当前线程就会执行挂起阻塞的步骤。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
//获取剩余的信号量
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
- 和ReentranLock一样,有Sync(继承AQS)、NonfairSync、FairSync三个内部类。默认实现也是NonfairSync
- 信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。
- 从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。