AQS是AbstractQueuedSynchronizer的缩写,AQS是Java并包里大部分同步器的基础构件,利用AQS可以很方便的创建锁和同步器。它封装了一个状态,提供了一系列的获取和释放操作,这些获取和释放操作都是基于状态的。它的基本思想是由AQS负责管理同步器类中的状态,其他的同步器比如可重入锁ReentrantLock, 信号量Semaphore基于各自的特点来调用AQS提供了基础能力进行状态的同步。
在AQS的Javadoc里面提到它是CLHLock的变种,在聊聊高并发(八)实现几种自旋锁(三) 这篇文章中我们说了如何利用CLH锁来构件自旋锁,回顾一下CLHLock的一些基本特点:
1. CLHLock是一种队列自旋锁的实现,提供了FIFO先来先服务的公平性
2. 利用一个原子变量AtomicReference tail的CAS操作来构件一个虚拟的链式结构
3. 节点Node维护一个volatile状态,维护一个prev指针指向前一个节点,获取锁时每个线程在prev节点的状态上自旋
4. 当线程释放锁时,只需要修改自身状态即可,后续节点会观察到volatile状态的改动而获取锁
AQS既然是CLHLock的一种变种,那么
1. 也维护以了一个基本的队列结构
2. 也是提供了一个Tail指针从队尾通过CAS操作入队列。
3. 提供了一个volatile类型的int值来维护状态
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
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);
}
..................
}
与标准CLHLock实现不同的是,AQS不是一个自旋锁,它提供了更加丰富的语意:
1. 提供了独享(exclusive)方式和共享(share)方式来获取/释放,比如锁是独占方式的,信号量semaphore是共享方式的,可以有多个线程进入临界区
2. 支持可中断和不可中断的获取/释放
3. 支持普通的和具有时间限制的获取/释放
4. 提供了自旋和阻塞的切换,可以先自旋,如果等待时间长,可以阻塞
/**
* The number of nanoseconds for which it is faster to spin
* rather than to use timed park. A rough estimate suffices
* to improve responsiveness with very short timeouts.
*/
static final long spinForTimeoutThreshold = 1000L;
AQS定义了两个内部类来辅助它的实现,一个是Node定义了队列中的节点,另一个是ConditionObject,是Condition接口的实现类,负责管理条件队列。关于条件队列更多内容可以看这篇 聊聊高并发(十四)理解Java中的管程,条件队列,Condition以及实现一个阻塞队列
先看下Node类,它比CLHLock中的Node有更多属性,除了完成基本的队列功能,还维护了是独享还是共享的模式信息
1. 维护了一个Node SHARED引用表示共享模式
2. 维护了一个Node EXCLUSIVE引用表示独占模式
3. 维护了几种节点等待的状态 waitStatus, 其中CANCELLED = 1是正数,表示取消状态,SIGNAL = -1,CONDITION = -2, PROPAGATE = -3都是负数,表示节点在条件队列的某个状态,SIGNAL表示后续节点需要被唤醒
4. 维护了Node prev引用,指向队列中的前一个节点,通过Tail的CAS操作来创建
5. 维护了Node next引用,指向队列中的下一个节点,也是在通过Tail入队列的时候设置的,这样就维护了一个双向队列
6. 维护了一个volatile的Thread引用,把一个节点关联到一个线程
7. 维护了Node nextWaiter引用,指向在条件队列中的下一个正在等待的节点,是给条件队列使用的。值得注意的是条件队列只有在独享状态下才使用
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** 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;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
再看一下ConditionObject,它是条件Condition接口的具体实现,维护了一个条件队列,条件队列是通过Node来构件的一个单向链表结构。底层的条件操作(等待和唤醒)使用LockSupport类来实现,在这篇中我们说了LockSupport底层使用sun.misc.Unsafe来提供条件队列的park和unpark操作。聊聊高并发(十七)解析java.util.concurrent各个组件(一) 了解sun.misc.Unsafe类
1. 维护了一个Node firstWaiter引用指向条件队列的队首节点
2. 维护了一个Node lastWaiter引用指向条件队列的队尾节点
3. 条件队列支持节点的取消退出机制,CANCELLED节点来表示这种取消状态
4. 支持限时等待机制
5. 支持可中断和不可中断的等待
我们来看几个典型的条件队列的操作实现
往条件队列里面加入一个等待节点,这个是await()方法的基本操作
1. 判断尾节点的状态是不是等待某个条件的状态(CONDITION),如果不是,就把CANCELLED节点从队列中踢出,然后把自己标记为尾节点
public class ConditionObject implements Condition, java.io.Serializable {
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
.................
}
从条件队列中唤醒一个节点,实际上doSignal只是把一个节点从条件队列中移除,然后加入到同步队列,并设置它在同步队列的前置节点的waitStatus = SIGNAL, 如果设置失败或者取消在条件队列等待,直接把这个节点的线程unpark唤醒,需要注意的是unpark操作只是把线程从等待状态转化为可运行状态,并不直接获得锁。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
Java线程的几种状态如下
支持中断的等待操作, 主要做了两个事情:新建一个Node进入条件队列等待被唤醒;从同步队列中移除并释放锁。它会相应线程的中断抛出中断异常,并且记录中断状态
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
不可中断的等待,也是先进入条件队列等待,并从同步队列出队列,释放锁。但是它不相应线程中断状态
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
限时等待,也是先进入条件队列等待,然后释放锁。轮询等待时间,当超时后再次进入同步队列,等待获得锁。如果获得了锁,就返回false. 如果在等待时被唤醒,就进入同步队列,等待获得锁,如果获得锁就返回true
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
if (unit == null)
throw new NullPointerException();
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
long lastTime = System.nanoTime();
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
AQS使用了Unsafe直接操作内存来对字段进行CAS操作和设置值。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
这篇介绍了AQS的基本情况,重点介绍了Node类和ConditoinObject类。下一篇会介绍AQS自身的重要方法。