JUC之AQS解读
一、队列同步器
AbstractQueueSynchronizer,即队列同步器,通常被称为AQS,是用于构造锁、同步组件的基础框架,它使用int表示同步状态,使用内置FIFO双向队列来完成线程的排队工作。
AQS可以说是整个juc锁框架的基础,可以这么说,AQS是面向实现锁,而同步锁是用于处理并发的场景。
二、设计思想
1、同步队列节点
一个node节点的信息如下:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
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() {
}
Node(Node nextWaiter) {
this.nextWaiter = nextWaiter;
THREAD.set(this, Thread.currentThread());
}
Node(int waitStatus) {
WAITSTATUS.set(this, waitStatus);
THREAD.set(this, Thread.currentThread());
}
final boolean compareAndSetWaitStatus(int expect, int update) {
return WAITSTATUS.compareAndSet(this, expect, update);
}
final boolean compareAndSetNext(Node expect, Node update) {
return NEXT.compareAndSet(this, expect, update);
}
final void setPrevRelaxed(Node p) {
PREV.set(this, p);
}
// VarHandle mechanics
private static final VarHandle NEXT;
private static final VarHandle PREV;
private static final VarHandle THREAD;
private static final VarHandle WAITSTATUS;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
//对Node节点中的状态等信息变量的操作转化为CAS操作。
NEXT = l.findVarHandle(Node.class, "next", Node.class);
PREV = l.findVarHandle(Node.class, "prev", Node.class);
THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
分析:Node节点中的一些状态的信息的改变,都是采用CAS操作。
2、同步状态处理(独占)
当线程获取同步状态失败,就会被构造成一个Node加入队列,同时阻塞该线程,直到同步状态释放,就会唤醒线程,使其尝试获取同步状态。
当一个线程获取同步状态失败,在加入同步队列的这个过程必须是线程安全的,因此,采用CAS加入队列。
/**
* CASes tail field.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return TAIL.compareAndSet(this, expect, update);
}
以下是获取独占锁的实现:
public final void acquire(int arg) {
//如果获取失败,并且成功加入同步队列,则对该线程进行中断。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
Node node = new Node(mode);
//通过死循环CAS尝试加入队尾。
for (; ; ) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (; ; ) {
final Node p = node.predecessor();
//只有前驱节点是头节点才能够获取同步状态
if (p == head && tryAcquire(arg)) {
//上升为头节点
setHead(node);
p.next = null; // help GC
//成功获取同步状态,中断标志为false。
return interrupted;
}
//前驱节点不是头节点,则采用park挂起,并且进行中断。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
以上就是同步独占锁的获取机制:
-
获取同步锁失败则进入队列,采用CAS尝试加入队列尾
-
每个队列节点都通过自旋尝试获取同步状态
- 判断前驱是否为头节点,是则进行同步状态的获取,并且线程的中断标识为false
- 如果前驱不是头节点,则采用park进行挂起,并且设置中断标识为true
以上是同步锁的获取过程,以下是释放同步状态的实现:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
可以看到,同步器的释放,是采用unpark唤醒后继节点。
3、同步状态处理(共享)
除了独占锁,还支持共享锁,共享锁的意思是同一时刻能有多个线程获取同步状态。
我们先看看共享锁是如何获取同步状态。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以发现,其实共享锁与独占锁的获取方式是相同的,只不过,共享锁允许多个线程同时持有同步状态,而独占锁则是同步状态只允许一个线程获取。
question
这里,可能有一个疑问,共享锁可以有多个线程获取,那么,与不加锁的区别在哪里,不就成了共享的么?
answer
- 共享锁确实可以由多个线程获取,共享资源对于这些获取了共享锁的线程来说,就是共享的。
- 这是DougLea大师的一个设计,确实,都是读资源共享可以不加锁,但是,通过加读锁,可以实现“读写分离”,也可以控制读并发的数量。