JavaDoc定义
AQS提供一个框架来实现基于FIFO等待队列的阻塞锁和相关的synchronizers(semaphores信号量,events事件等等)。
它是为了大多数synchronizers而设计的基础类,这些synchronizers都依赖于一个原子的int值来代表状态state。它的子类必须定义一个protected方法来修改state值,这个方法定义了这个state值对于这个对象被获取和被释放意味指什么。考虑到这些,该类的其他方法执行所有的队列和阻塞机制。
子类可以维护其他的state字段,但是只有使用getState,setState和compareAndSetState方法自动更新的int值才会被同步跟踪。
子类需要被定义成非public的内部帮助类,用于实现它们包装类的同步属性。类AbstractQueuedSynchronizer没有实现任何的同步接口。相反,它定义了类似acquireInterruptibly这样的方法,这些方法可以被具体的锁和相关的synchronizers来实现它们的公共方法。
这个类独占模式Exclusive和共享模式Shared的任一种或者全部两种。当使用独占模式获取锁时,其他线程无法尝试获取到锁。共享模式可以被多个线程获取成功(但不是必须的)。
在不同模式中等待的线程共享相同的FIFO队列。通常,实现的子类只支持一种模式(独占、共享),但是在ReadWriteLock(读写锁)中它们同时支持了两种模式。只支持独占或共享模式的子类不需要定义支持未使用模式的方法。
AQS源码解读
常用的字段
head: 等待队列头部
tail:等待队列尾部
acquire 获取独占锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先尝试获取独占锁,tryAcquire方法交由具体的锁实现。如果获取成功,就不管了。
如果获取失败,那么就要进行加入等待队列的操作。怎么加入等待队列的呢,先看一下addWaiter方法
addWaiter
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;
}
新建一个Node,Node里保存了线程为当前线程,模式为Node.EXCLUSIVE独占模式。pred指向等待队列尾部。
如果pred不为空,那么就把当前线程的前一个线程指向pred,在cas pred为当先线程。如果cas成功,pred的next就指向当前线程,返回当前线程。见下图
enq
如果pred即tail为空呢?直接进行了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;
}
}
}
}
tail为空,意味着等待队列为空。但是这个等待队列并不是类似LinkedBlockingQueue之类的数据结构。所以,AQS建立了一个辅助节点Node,存了个空线程,作为队列的头部。其他的线程都往尾部加。
那么AddWaiter看好了,再看看acquireQueued做了什么。
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 获取当前线程的prev线程
if (p == head && tryAcquire(arg)) { // 如果当前线程的prev是head,那么就代表head是那个辅助Node,当前线程就是实际上的队列头部,执行尝试获取锁。
setHead(node);// 如果获取成功,执行setHead
p.next = null; // help GC,原先的辅助节点的next指向空
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 当获取锁失败了是否需要阻塞
parkAndCheckInterrupt()) // 如果需要Park,使用LockSupport.park()阻塞并且检查中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 如果执行失败了,取消获取锁, 把当前线程状态设置为CANCEL
}
}
setHead(node) 这行代码看上去有点奇怪,获取锁成功不是应该出队列吗?怎么字面意思好像是还把当前线程设为header Node。那么看看它的具体实现
setHead
private void setHead(Node node) {
head = node; // head 设置为当前线程
node.thread = null; // node的线程字段指向null
node.prev = null; // node的前节点指向null
}
一目了然吧,这个Node是不是完全符合辅助节点的定义。
shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //前继节点还在等待触发,还没当前节点的什么事儿,所以当前节点可以被park
return true;
if (ws > 0) { // 前继节点是CANCELLED ,则需要从同步队列中删除,并检测新接上的前继节点的状态,若还是为CANCELLED ,还需要重复上述步骤
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //到这一步,waitstatus只有可能有2种状态,一个是0,一个是PROPAGATE,无论是哪个都需要把当前节点的状态设置为SIGNAL,-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
等待队列中,正常情况下前继节点的等待状态为SIGNAL
release
直接看源码了
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁,由子类实现
Node h = head; // 释放锁成功,h指向head
if (h != null && h.waitStatus != 0) // head不为空并且head的状态不为0
unparkSuccessor(h); // unpark,唤醒等待队列,并返回成功
return true;
}
return false; // 释放锁失败,直接返回false
}
private void unparkSuccessor(Node node) { // node指的是head
int ws = node.waitStatus;
if (ws < 0) // 如果当前Node等待状态<0,直接设置为0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // s指向当前Node的下一个Node
if (s == null || s.waitStatus > 0) { // 如果下一个是空的或者等待状态是CANCELLED
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t; // 从尾部开始从前找,找到最前面的waitStatus<=0的Node
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒Node中的线程
}
acquireShared
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) { // 如果前置为head
int r = tryAcquireShared(arg); // 尝试获取资源,返回资源剩余的数量
if (r >= 0) { // 拿到资源
setHeadAndPropagate(node, r); // 修改head节点
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
releaseShared 唤醒
这里有个问题,为什么只唤醒了一次,却能够唤醒等待队列的所有线程?
看了一下jvm源码,调用了unsafe.unpark本地方法。这个方法似乎会重复调用。
void WatcherThread::unpark() {
MutexLockerEx ml(PeriodicTask_lock->owned_by_self() ? NULL : PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
PeriodicTask_lock->notify(); // 通知
}
bool Monitor::notify() {
assert (_owner == Thread::current(), "invariant") ;
assert (ILocked(), "invariant") ;
if (_WaitSet == NULL) return true ;
NotifyCount ++ ;
// Transfer one thread from the WaitSet to the EntryList or cxq.
// Currently we just unlink the head of the WaitSet and prepend to the cxq.
// And of course we could just unlink it and unpark it, too, but
// in that case it'd likely impale itself on the reentry.
Thread::muxAcquire (_WaitLock, "notify:WaitLock") ;
ParkEvent * nfy = _WaitSet ; // 等待队列
if (nfy != NULL) { // DCL idiom
_WaitSet = nfy->ListNext ; // 等待队列指向下一个元素
assert (nfy->Notified == 0, "invariant") ;
// push nfy onto the cxq
for (;;) {
const intptr_t v = _LockWord.FullWord ;
assert ((v & 0xFF) == _LBIT, "invariant") ;
nfy->ListNext = (ParkEvent *)(v & ~_LBIT);
if (CASPTR (&_LockWord, v, UNS(nfy)|_LBIT) == v) break;
// interference - _LockWord changed -- just retry
}
// Note that setting Notified before pushing nfy onto the cxq is
// also legal and safe, but the safety properties are much more
// subtle, so for the sake of code stewardship ...
OrderAccess::fence() ;
nfy->Notified = 1;
}
Thread::muxRelease (_WaitLock) ;
if (nfy != NULL && (NativeMonitorFlags & 16)) {
// Experimental code ... light up the wakee in the hope that this thread (the owner)
// will drop the lock just about the time the wakee comes ONPROC.
nfy->unpark() ; // 如果等待队列不会空,继续调用unpark
}
assert (ILocked(), "invariant") ;
return true ;
}
不知猜想是否正确