1.概述
JDK 1.5之后提供了JUC线程同步包,AQS就是该包下面提供的一个线程同步框架,提供给用户在实现多线程并发编程时使用。AQS(Abstract Queued Synchronizer)抽象同步队列,既然是一个抽象类,那么就需要用户实现该类。JUC包下面,已经提供了一些通过AQS实现的同步工具栏,例如CountDownLatch(倒计数门闩)、ReentranLock(重入锁)、ReentranReadWriteLock、Semaphore(信号量)、SyclicBarrier(循环屏障)等。
2.AQS核心思想
AQS核心思想是提供了一个volatile修饰的int类型的状态值state,通过state状态控制同步器是否被获取,然后同步器分为两种模式,独占模式(exclusive)和共享模式(shared)。独占模式下只有一个线程可以持有锁,共享模式下,多个线程可以同时持有同一个锁。然后AQS提供了一个双向队列CLH,队列有head(头节点)和tail(尾节点),由线程封装为node节点对象组成的队列,在acquire获取锁的时候,如果所锁正在被占用,则将现场加入队列尾部挂起等待。
3.AQS源码实现
3.1内部类Node节点
static final class Node {
//共享模式
static final Node SHARED = new Node();
//独占模式
static final Node EXCLUSIVE = null;
//waitStatus等于1表示当前线程取消竞争资源
static final int CANCELLED = 1;
//表示后续节点可以取消挂起
static final int SIGNAL = -1;
//当前线程正在等待某个条件
static final int CONDITION = -2;
//下一个共享节点无条件传播
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;
}
}
3.2.AQS主要属性
// 头节点
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;
}
4.AQS核心方法
作为一个synchronizer,主要的操作便是获取(acquire)和释放(release)
在获取锁的过程中,我们有两种场景,一种是尝试去获取,如果获取到则返回true,获取不到返回false,那么如果获取不到锁,线程就去做其他的事情,不会等待;另一种场景便是必须获取到锁,完成任务,如果获取不到则进行等待。AQS也分别提供了两个方法tryAcquire()、acquire()
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
这个方法非常简单,直接返回不支持操作异常,所以需要使用者实现该方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先调用tryAcqure方法,如果直接返回true,该方法直接结束,如果返回false,则需要将当前线程加入到队列中,使用addWaiter方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 首先尝试快速加入队列,如果失败则使用enq完整的加入队列方法
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
addWaiter方法首先会进行会进行快速入队,当入队失败之后,选择适用enq,完整的入队方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法内部使用循环,直到将node加入到队列尾部,成功返回tail节点。该方法中有两个原子操作方法,分别是compareAndSetHead设置队列头部head节点,compareAndSetTail设置队列尾部节点。当第一个节点进入的时候,首先new 了一个新的节点,然后再次循环到else分支,将node节点前阶段执行head,node修改为了tail节点。
接下来会执行acquireQueued()方法
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);
}
}
整个过程: