一 引读
对AQS不够了解的同学,请务必先观看博客
《大厂面试必会AQS(1)——从ReentrantLock源码认识AQS》
需要首先了解的知识:
1.Condition
Condition内部主要是由一个装载线程节点 Node 的 Condition Queue 实现
对 Condition 的方法(await, signal等) 的调用必需是在本线程获取了独占锁的前提下 所以 Condition Queue 内部是一条不支持并发安全的单向 queue (这是相对于 AQS 里面的 Sync Queue)而Sync Queue同步对类是支持并发的双向链表
2.ConditionObject
ConditionObject是AQS的一个内部类,实现Condition接口,类中的两个重要成员变量firstWaiter和lastWaiter(条件队列的首个、最后一个Node节点)
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
常量
/** Mode meaning to reinterrupt on exit from wait */
//从等待退出时切换为中断状态
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
//从等待退出时抛出异常
private static final int THROW_IE = -1;
实例方法很多,因为实现了Condition接口,因此await()和signal()为本文重点
3.与同步队列的联系
(1)同步队列依赖一个双向链表来完成同步状态的管理,当前线程获取同步状态失败 后,同步器会将线程构建成一个节点,并将其加入同步队列中;而条件队列是单向链表
(2)通过signal或signalAll将条件队列中的节点转移到同步队列
(3)调用await方法进行阻塞(从同步队列转化到条件队列)
(4)同步队列拥有的prev前驱节点和next后继节点,条件队列只有nextWaiter即下一个等待节点。
(5)一个线程只能存在于两个队列中的一个
二 await源码分析
await方法使当前线程阻塞等待,直到被通知或者被中断。
与该condition关联的lock会被自动释放,并且由于线程调度的原因线程变得不可用,其他线程调用了这个condition的signal()方法或者signalAll()方法,或者是其他线程调用当前线程的Thread.interrupt()方法;
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);
//检查是否被中断过,没有中断则为0
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
//如果被中断过跳出当前循环
break;
}
//获取独占锁同时传入挂起前保存的资源值saveState
//如果interruptMode != 异常,则调整interruptMode的值为REINTERRUPT,接下来会让线程线程中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//从条件队列中解除cencelled节点。
unlinkCancelledWaiters();
if (interruptMode != 0)//如果线程中断过
//抛异常或者中断线程
reportInterruptAfterWait(interruptMode);
}
await方法大致分为
1.前置检查:如果线程被中断,抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
2.将持有锁的线程包装为node,放入等待队列,删除等待队列中非CONDITION节点
Node node = addConditionWaiter();
/**
* 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();//清除cancelled的节点
t = lastWaiter;
}
//包装节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)// 尾结点为空,队列为空,初始化队列
firstWaiter = node;
else
t.nextWaiter = node;//从尾结点添加
lastWaiter = node;
return node;
}
addConditionWaiter方法调用了unlinkCancelledWaiters来清除cancelled的节点
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;//辅助指针,上一个有效节点
while (t != null) {
Node next = t.nextWaiter;//next指针
if (t.waitStatus != Node.CONDITION) {
//如果当前节点状态不是CONDITION,删除当前节点
t.nextWaiter = null;
if (trail == null)//因为是指向上一下有效节点,如果为null,首节点指向被移除节点的下一个节点
firstWaiter = next;
else
//否则彻底删当前节点t
trail.nextWaiter = next;
if (next == null) // 如果next为空,说明队列遍历完成,将尾指针指向辅助节点
lastWaiter = trail;
}
//如果当前节点状态是CONDITION,即有效节点
else
//辅助指针,指向当前有效节点
trail = t;
//t指针后移,开始遍历下一个节点。如果next == null,则遍历结束
t = next;
}
}
unlinkCancelledWaiters就是遍历等待队列,将非CONDITION状态到的节点移除。
3. 释放节点持有的锁,并唤醒后继节点,返回持有的锁资源
int savedState = fullyRelease(node);
/**
* Invokes release with current state value; returns saved state.
* Cancels node and throws exception on failure.
* @param node the condition node for this wait
* @return previous sync state
*/
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//获取当前线程的锁状态
//释放锁资源唤醒后继节点
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
fullyRelease方法会释放当前锁资源,并返回释放的savedState 在被唤醒或者被中断后重新竞争锁时作为入参,保证重入锁的安全性,其中的release方法《大厂面试必会AQS(1)——从ReentrantLock源码认识AQS》已经讲过,不再赘述。
4.如果节点不在同步队列中,即还在条件队列中,阻塞当前线程直至被中断唤醒或者被其他线程唤醒
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
首先看isOnSyncQueue(node)方法
final boolean isOnSyncQueue(Node node) {
//节点状态为CONDITION(说明在等待队列中)、或者前置节点为空,是一个独立节点,证明不再同步队列,则处于等待队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;//如果有后继节点,肯定在同步队列中,即不处于等待队列
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
//走到这一步,虽然前驱节点不为空,但是不保证一定在同步队列,因为cas会失败,因此需要从尾部遍历队列来保证它不在队列上。
return findNodeFromTail(node);
}
看一下findNodeFromTail方法是从队列尾部往前找,找到返回true,//找到最后都没有,返回false
/**
* Returns true if node is on sync queue by searching backwards from tail.
* Called only when needed by isOnSyncQueue.
* @return true if present
*/
private boolean findNodeFromTail(Node node) {
Node t = tail;//从队列尾部往前找
for (;;) {
if (t == node)//找到返回true
return true;
if (t == null)//找到最后都没有,返回false
return false;
t = t.prev;
}
}
如果isOnSyncQueue返回false,调用LockSupport.park(this)将当前线程挂起直至被中断唤醒或者被其他线程唤醒,退出while循环的关键点在于
1:checkInterruptWhileWaiting方法。被中断返回1或者-1break出whhile循环
2:isOnSyncQueue返回true,当前condition被调用signal或signalAll方法,唤醒线程并且加入到同步队列
下面分析checkInterruptWhileWaiting方法
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?//被中断,调用transferAfterCancelledWait方法
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
//CAS跟新Node的状态从CONDITION -2到0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//CAS成功,当前节点插入同步队列
enq(node);//该方法不再赘述
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
//如果忘记调用signal,那么就不能继续执行了直到它回到同步队列中。
while (!isOnSyncQueue(node))
Thread.yield();// 让出CPU
return false;
}
可以看到线程被唤醒后,检查线程状态,如果是中断状态,要尝试将node的节点状态变更为0,如果变更成功,则判定中断原因是异常,如果变更失败,要给线程时间让其他线程将node放回同步队列。
5.当循环结束,后续流程就需要 让线程重新进入锁竞争状态并根据中断状态对线程进行合理的处理
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
acquireQueued方法不再赘述,会循环竞争锁资源并唤醒后继节点
如果竞争的时候发生了中断则返回true,接下来如果interruptMode != 异常,则调整interruptMode的值为REINTERRUPT。接下来如果节点nextWaiter 不为null,清空等待队列的cancelled节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)//如果被中断
//中断或者抛异常
reportInterruptAfterWait(interruptMode);
如果中断过,调用reportInterruptAfterWait方法根据interruptMode抛出异常或者再次中断
/**
* Throws InterruptedException, reinterrupts current thread, or
* does nothing, depending on mode.
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
总结:
1、前置检查:如果线程被中断,抛出中断异常
2、将持有锁的线程包装为node,放入等待队列,删除等待队列中非CONDITION节点
3、释放节点持有的锁,并唤醒后继节点,返回持有的锁资源
4、如果节点不在同步队列中,即还在条件队列中,阻塞当前线程直至被中断唤醒或者被其他线程调condition.signal/condition.signalAll方法唤醒
5、当线程被唤醒,后续流程就需要 让线程重新进入锁竞争状态
6、如果申请成功,要根据中断状态对线程进行合理的处理:抛异常或中断
二 线程唤醒signal()源码分析
唤醒等待线程。
如果存在线程在条件上等待,选择其中一个作为被唤醒的线程。
在调用该方法时,要求当前线程持有与condition关联的Lock。否则抛出IllegalMonitorStateException。
public final void signal() {
if (!isHeldExclusively())//当前线程不持有锁抛异常
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}~
可以看到如果当前线程不持有锁抛异常,然后取等待队列的第一个节点来唤醒doSignal(first)
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
//没有后续节点,清空队列
lastWaiter = null;
//将头结点从等待队列中移除
first.nextWaiter = null;
// while中transferForSignal方法对头结点做真正的处理~
} while (!transferForSignal(first) &&
//下一个节点
(first = firstWaiter) != null);
}~
如果没有后续节点,清空队列,将头结点从等待队列中移除,在while中循环唤transferForSignal(first)直至成功唤醒一个节点
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;//获得前驱节点状态
//如果节点取消或者不能变更为SIGNAL状态,需要唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}~
这里有个疑问:signal方法,为什么不像Object.notify方法那样,最终总会调用unpark方法唤醒线程,而是限制了条件呢
从前面的知识中可一知道,同步队列的头结点释放锁会唤醒后继节点的线程,有个前提条件是,节点的前驱节点的waitStatus必须为SIGNAL,才会在释放锁时唤醒后继节点。所以,如果前驱节点的waitStatus为cancelled,或者将其waitStatus设置为SIGNAL的cas操作执行不成功时,就需要在这里调用unpark方法唤醒线程。
最后,如果对于为什么中断可以唤醒线程有兴趣的同学这里推荐两篇博客:
《理解java的interrupt机制》通过demo理解中断
《jvm源码分析之interrupt()》从jvm源码理解中断