一、简介
AbstractQueuedSynchronizer简称AQS,从名字理解,它是一个抽象队列同步锁,是重入锁、读写锁的基类。如果我们要全面学习java的并发锁,首先要掌握AQS的加锁机制。下面文章我将介绍AQS的结构,加锁和解锁方法。
二、什么是AQS
1.AQS初认识
AQS的加锁原理可以用下面的图表示,每次调用加锁时,会对int类型的成员变量state进行判断。
加锁:当state不等于0时,会把当前节点加到链表中,阻塞等待,直到被前驱唤醒,head指向自己时才会去争取锁。争取锁的时候是用了unsafe.compareAndSwapInt方法,其实AQS里面使用了大量Unsafe里的方法,它是不安全的,我们一般很少直接调用,但是人家Doug Lea大神却使用得游刃有余。
当state等于0,并且调用compareAndSwapInt方法成功时,表示获得了锁。
解锁:获得锁的线程在调用解锁方法后,会把head指向到下一个节点,并且唤醒下一个节点去竞争获取锁。
2.AQS类关系图
从关系图中可以看到AQS继承了AbstractOwnableSynchronizer,与AbstractQueuedLongSynchronizer并列关系。
AbstractOwnableSynchronizer主要有两个方法,用户保存和获取占用锁的线程。
AbstractQueuedLongSynchronizer里面的方法实现基本与AbstractQueuedSynchronzier相同,名字的差别在于Long,其实也就是他的state成员变量是Long类型。它作用表现在可以保存更多的状态位,在锁重入、读写锁的数量方面有更多的选择。估计很少场景需要用到它。
3.基本数据结构
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
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;
/** 代表线程在另外一个队列或者链表中等待,可以把condition理解成第二等待队列,只有从第二队列移到第一队列才能参与锁的竞争 */
static final int CONDITION = -2;
/** 传播状态,指示后继节点可以无条参与读锁的竞争 */
static final int PROPAGATE = -3;
/** 等待状态,为以上几种类型 */
volatile int waitStatus;
/** 上一个节点 */
volatile Node prev;
/** 下一个节点 */
volatile Node next;
/** 当前线程 */
volatile Thread thread;
/** 在conditin等待队列中,指向下一个等待节点 */
Node nextWaiter;
}
// 可以理解为它就是第二等待队列
public class ConditionObject implements Condition, java.io.Serializable {
/** 指向第一个等待节点 */
private transient Node firstWaiter;
/** 指向最后一个等待节点. */
private transient Node lastWaiter;
}
}
三、AQS的使用
1.加锁
// 一般加锁会首先调用这个方法,参数为加锁的计数器,累加到成员变量state
public final void acquire(int arg) {
// 如果不能立刻,则加入到队列中去获取
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 子类实现,一般是state成员变量加上arg
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 这里把一个节点加到队列末端
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 入队列
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;
}
}
}
}
// 等待队列的节点获取到锁会返回true,否则阻塞等待
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果上一个节点是头节点,说明可以参与锁竞争了。并且锁竞争成功则当前节点占用锁,把head指向自己
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 竞争锁失败,判断是否阻塞线程,下面分析这个方法
if (shouldParkAfterFailedAcquire(p, node) &&
// 这里是阻塞线程,用的是LockSupport.park(this)
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 失败则说明是有异常了,则取消该线程的等待。后面分析这方面
cancelAcquire(node);
}
}
// 竞争锁失败时,判断线程是否需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱状态时signal,则需要阻塞等待唤醒
return true;
if (ws > 0) {
// 前驱状态大于0,说明是取消的状态,则向前遍历找到不大于0的前驱
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 到这一步说明前驱的状态要么是0,要么是PROPAGATE(传播状态),则当前线程可以再次参与锁竞争一次,如果第二次还没有获取到,则前驱状态变为sigal了会到第一步阻塞线程
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 取消锁竞争
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// 如果前驱为取消状态,则快速断开所有这些前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 前驱的下一个节点,
Node predNext = pred.next;
// 设置当前节点状态为取消
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,则设置前驱为尾节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
// 如果前驱不为头节点,并且(前驱状态是sigal,或者状态小于0并且设置状态sigal成功),并且前驱线程不为null,则设置前驱的下一个节点为当前节点的下一个节点
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 到这步说明需要参与锁竞争了
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
2.解锁
// 解锁相对简单
public final boolean release(int arg) {
// 这里一般是状态state减去arg
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
四、总结
AQS是重入锁和同步队列的基础类,是学习JUC的第一课。以上是我对AQS的了解,如有不对之处欢迎指正。