更多java源码分析请见:jdk11源码分析系列文章专栏:Java11源码分析
在jdk11源码-ReentrantLock源码一文中分析了ReentrantLock源码。里面有讲述在多个线程加入队列时的AQS内部状态:
简单来说:condition的await和signal操作就是将node节点在这两个队列中转移的过程,这里重点关注waitstatus和nextwaiter两个字段。后面会逐行代码分析
一个ReentrantLock可以创建多个Condition
Condition condition = lock.newCondition();
实际是创建一个ConditionObject对象,ConditionObject的定义在AbstractQueuedSynchronizer中。
在之前的文章中介绍了,一个node对象中有两个重要的对象属性:
volatile int waitStatus;
Node nextWaiter;
waitStatus
已经在jdk11源码-ReentrantLock源码一文中讲述:
static final int CANCELLED= 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
nextWaiter
总共有三种类型的值:
condition queues condition队列仅在独占模式有效,使用一个简单的链接队列来保存因condition而等待的节点(线程)。这些节点可以被转移到AQS队列中重新获取锁。
condition.await();使当前线程阻塞
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();//将waiter加入到condition等待队列(condition queue)
int savedState = fullyRelease(node);//将当前线程占用的state锁资源全部释放。目的是为了将该线程从AQS队列中移除。
int interruptMode = 0;
//isOnSyncQueue:在AQS队列中返回true,否则返回false
while (!isOnSyncQueue(node)) {
LockSupport.park(this);//执行这里,说明当期线程不在AQS队列中,则需要被park挂起。
//这里是被唤醒后的执行逻辑
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//检查在park期间,是否被中断,根据具体情况来决定抛异常还是继续中断
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 清除被取消的节点
unlinkCancelledWaiters();
if (interruptMode != 0)//不等于0,表示park期间被中断过
//当前线程被唤醒后,通过interruptMode来决定是继续中断还是抛异常
reportInterruptAfterWait(interruptMode);
}
//将waiter加入到等待队列
private Node addConditionWaiter() {
if(!isHeldExclusively())
throw new IllegalMonitorStateException();
Node t = lastWaiter;
// 如果 lastWaiter 是取消状态(waitStatus != Node.CONDITION),那么将其清除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();//删除condition队列中所有被取消的节点
t = lastWaiter;
}
//新建一个waitStatus是Node.CONDITION的节点,表示当前节点(线程)的等待状态是condition。
Node node = new Node(Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
addConditionWaiter方法是将当前线程加入到condition的等待队列,lastWaiter 指向当前最新加入的node。
final int fullyRelease(Node node) {
try {
int savedState = getState();
if (release(savedState))
return savedState;
throw new IllegalMonitorStateException();
} catch (Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
}
fullyRelease方法有必要看一下,他首先获取state的值savedState,然后执行release(savedState),release方法之前已经分析过了,他会释放savedState数量的资源。这里也就是将当前线程锁定的资源全部释放,即当前线程释放独占锁。
也比较容易理解,condition只与独占锁配合使用,一个线程获取锁,但是被condition await以后挂起,肯定需要释放锁,其他线程才有机会获取锁而继续执行。
根据前面的分析,condition的await会将线程node添加到condition队列,然后从AQS队列移除。在signal时,会将该线程node添加到AQS阻塞队列中。有关signal后面会分析。
在释放了独占锁以后,就通过isOnSyncQueue
方法循环检查当前线程(node)是否在AQS队列中,如果已经从AQS队列中移除,那么就可以放心的将其park了。
在park期间,线程有可能被中断或者unpark唤醒。那么就要判断接下来是需要继续park还是抛异常还是去重新获取锁。
final boolean isOnSyncQueue(Node node) {
//如果当前线程node的状态是CONDITION或者node.prev为null时说明已经在Condition队列中了,所以返回false;
//如果node添加到AQS阻塞队列中,那么他的waitstats会被初始化为0,或者被修改为-1,-3,肯定不是condition(-2)
//如果node添加到AQS阻塞队列中,那么他的prev肯定不为空,至少也是head节点
if (node.waitStatus == Node.CONDITION || node.prev == null) return false;
//如果node有后继节点,那么他肯定在队列中。因为前面分析了,condition队列是不会设置next字段值的
if (node.next != null) return true;
/*
* 执行到这里,说明node的waitStatus 不是CONDITION ,prev肯定也不是null。并且next肯定为null。
* 第一次看这个代码,肯定会蒙圈,怎么可能会出现这种不一致的情况呢
* 这种情况是因为在将node添加到AQS阻塞队列时,采用的CAS策略。CAS就有可能失败,所以会出现这种临时的不一致行为。
* 在下面分析signal时会看到,signal会调用AbstractQueuedSynchronizer#enq方法,这个方法会先设置prev,然后再CAS设置tail和next。
*/
return findNodeFromTail(node);
}
//新添加的节点都是加在队尾,所以从后向前找效率更高
private boolean findNodeFromTail(Node node) {
for (Node p = tail;;) {
if (p == node) return true;
if (p == null) return false;
p = p.prev;
}
}
大家可以看到在AQS分析过程中个,大部分遍历循环查找都是从tail开始向前查找的。这是因为新加入的节点都加在队尾,从后往前找效率更高。
await方法中包含被唤醒后的执行逻辑,这个在分析为signal以后再看。
public final void signal() {
//isHeldExclusively()就一句话,判断互斥锁是不是当前线程加的。getExclusiveOwnerThread() == Thread.currentThread()
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)//唤醒condition队列中的第一个等待节点
doSignal(first);
}
//从condition队列头往后找到一个没有被取消的节点,对其进行唤醒操作。
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
//上面while条件表示:移除失败,并且condition队列还有后继节点
}
//将node节点从condition队列移动到AQS阻塞队列,成功返回true。
final boolean transferForSignal(Node node) {
//设置失败,说明node被取消了。返回false,那么在上层循环中会继续查找下一个节点
//设置成功,则node的waitstatus=0,再加上上面他的nextWaiter 被设置为null,也就是从condition队列中移除了。
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
return false;
//将node以CAS方式添加到AQS队尾
//注意这里返回的p是老的队尾,也就是新加入node的前驱节点
Node p = enq(node);
int ws = p.waitStatus;
//ws > 0说明前驱节点被取消;
//ws<=0,那么需要将新加入节点的前驱节点waitstatus设置为Node.SIGNAL
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
LockSupport.unpark(node.thread);//唤醒
return true;
}
一句话总结唤醒操作signal的工作:将线程node从condition队列转移到AQS队列中。
从代码中可以看到,其实signal只是将node从condition队列移动到AQS队列,并没有主动调用LockSupport.unpark方法,还是依赖于AQS自身的机制真正unpark。
先看一下有哪些情况会让线程停止park继续往下执行:
回到await方法,看唤醒后继续执行的逻辑
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);
//唤醒后继续执行
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
/*
* 如果中断了,检查中断是发送在signalled之前还是之后,
* 如果中断发生在signalled之前,返回THROW_IE
* 如果中断发生在signalled之后,返回REINTERRUPT
* 没有中断返回0
*
* 注意:这里如果中断了,会首先会尝试将节点转移到AQS阻塞队列
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
/*
* 该方法只有线程中断才会执行
* 尝试将节点转移到AQS队列。如果在signalled前取消了线程,返回true,上层checkInterruptWhileWaiting方法会抛出异常标识
*
*/
final boolean transferAfterCancelledWait(Node node) {
//如果这里CAS成功,说明node节点当前的状态时CONDITION,也就是说中断操作是在signalled前发生的。
//因为:1、只有中断才会调用这个方法。2、上面doSignal方法中会在signal过程中将其状态由CONDITION修改为0。
if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
enq(node);//将node节点加在AQS阻塞队列的队尾
return true;
}
/*
* 执行到这里说明上面CAS失败,说明中断发生在signalled之后。
* signal方法中会将node以CAS方式添加到AQS队尾,执行enq方法,但是走到这里时enq方法可能还没有执行完,也就是node节点还没有完全加入到AQS队列中,所以这里自旋等待其完成。
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
上面将node从condition队列转移到AQS阻塞队列中以后,就开始尝试获取锁【注意:无论是否中断,都会将其转移到AQS阻塞队列】。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;//获取锁过程中被中断过,并且interruptMode 不是抛异常,那么重新设置中断标识。
acquireQueued方法用于获取锁资源。由于在await的时候,释放了savedState数量的资源,所以这里需要获取savedState数量的资源。acquireQueued返回结果是是否中断过,这里的中断是指在获取锁资源的过程中是否被中断过,与interruptMode 无关。
获取锁过程中被中断过,并且interruptMode 不是抛异常,那么重新设置中断标识。
接下来执行
if (node.nextWaiter != null)
unlinkCancelledWaiters();//清理condition队列中的被清除的节点
注意这里是清理的condition队列中的被取消的节点。
那么什么时候node.nextWaiter 不为空呢?在doSignal方法中会执行first.nextWaiter = null;
一行代码,正常情况下来说,nextWaiter 应该都为空才对。
注意这里只是正常情况,别忘了被中断的情况,如果在signal之前就被中断了,执行了上面的acquireQueued方法,转移到了AQS队列中,此时该node节点的nextWaiter 不为空。但是他的waitstatus已经被设置为0.
unlinkCancelledWaiters比较简单,就是遍历condition队列,清除被取消(waitStatus != Node.CONDITION))的节点。因为condition队列中的所有节点的waitStatus 肯定都是Node.CONDITION。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {//waitStatus != Node.CONDITION) 就是被取消的节点
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
最后一步就是根据checkInterruptWhileWaiting方法返回的interruptMode中断标记,来决定抛异常还是给当前线程设置中断标记
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
好了 ,condition分析完毕,还是有点复杂度的。
更多java源码分析请见:jdk11源码分析系列文章专栏:Java11源码分析