ConditionObject
ConditionObject是AQS中的内部类,用于线程间通信,可以精确地挂起或者唤醒某个线程。
ConditionObject中的成员变量是两个Node,分别构成FIFO队列的头节点和尾节点,condition队列中Node出队/入队,由每个节点Node中的等待状态(
static final int CONDITION = -2
)进行控制。
// condition queue的头节点
private transient Node firstWaiter;
// condition queue的尾节点
private transient Node lastWaiter;
一、awaite()方法
先看下ConditionObject
的awaite()
方法,它和Object
的waite()
方法一样,都是让线程等待,并且会释放锁。
当前线程在调用await()
方法后,会响应中断(抛中断异常),加入到条件队列,此时需要释放锁(能调用await方法的线程,必定持有了独占锁),且记录释放前锁状态,因为当线程重新被唤起时,需要通过acquireQueued()
方法申请锁,成功则,重新恢复锁状态。该方法中,会判断当前线程是否在同步队列中。一个线程在同步队列中有两种可能:
1、当前Thead在条件队列中被中断,checkInterruptWhileWaiting()
方法会将其加入同步队列。
2、其它线程调用signal()
,会转移到同步队列中
public final void await() throws InterruptedException {
//该方法会响应中断
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程添加到条件队列中
Node node = addConditionWaiter();
//线程调用await()方法时,是持有锁的,这里需要将其释放,并记录线程释放前锁状态
//释放锁,当后继未取消节点获取锁后,当前线程Node会移出同步队列
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断当前线程是否在同步队列
while (!isOnSyncQueue(node)) {
//在此挂起当前线程
LockSupport.park(this);
// 如果当前线程被中断,则将Node加入同步队列
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//走到这里说明当前线程在同步队列中,获取锁后,恢复锁释放前状态,返回线程中断状态。
//interruptMode != THROW_IE,表明不是在条件队列的等待过程中被中断
//有可能是被signal()唤醒,此时interruptMode==REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//通过 "node.nextWaiter != null" 表明,当前Node 在 Condition Queue 与 Sync Queue 里面都会存在,
//说明当前线程被唤醒方式是中断,而不是通过signal()
if (node.nextWaiter != null) // 清除cancel节点(非CONDITION状态节点)
unlinkCancelledWaiters();
//如果当前线程被中断,则根据线程的中断模式来作对应处理
//在条件队列中等待被中断:THROW_IE,则抛出异常
//被signal()唤醒,REINTERRUPT则重新设回中断状态
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
接下来挨个分析await()方法中调用的各种方法:
1.addConditionWaiter()
该方法负责将新的node插入条件队列未被取消的尾部节点后面,作为新的lastWaiter。
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter是取消状态,则直接踢出队列
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters(); //从头节点开始依次找寻被取消的节点,踢出条件队列
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//lastWaiter==null,表明条件队列不存在,需要初始化,当前节点指向firstWaiter和lastWaiter
//否则,直接插入条件队列尾部,作为新的lastWaiter
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
2.fullyRelease()
该方法用释放当前线程持有的锁,并返回释放锁前的锁状态,释放锁失败,则设置当前Node为取消状态
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 获取锁状态
if (release(savedState)) {//释放锁
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//当重入锁,重入后,锁状态sate大于1,此时release(savedState)返回false,进入else,抛异常,failed=true
//释放锁失败,当前Node的等待状态设置为取消
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
3.checkInterruptWhileWaiting(Node node)
该方法用于检测当前线程中断,如果signalled之前被中断,则返回THROW_IE,signalled之后被中断,返回REINTERRUPT,未中断返回0
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
4.transferAfterCancelledWait(Node node)
该方法用于取消线程在条件队列中等待,且确保线程能进入同步队列。
返回值true:表示当前线程是在被signaled前发生了中断;false:表示当前线程是在signaled后发生了中断
final boolean transferAfterCancelledWait(Node node) {
//signal之前被中断,取消CONDITION状态
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node); //将当前线程Node加入到同步队列中
return true;
}
/*
* 上面if如果不满足,很可能是另一个线程执行了signal(),导致node状态已经被改为0了,
* 这个时候,node正在入同步队列的过程中
* 此时当前线程让出资源,不需要处理,等待signal()执行完成,
* 这边检测到线程加入到同步队列中后,就返回false
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
5.isOnSyncQueue(Node node)
该方法用于检测条件队列中的Node是否也存在与CLH同步队列中。
当在条件队列中等待的线程,被中断,或者被其它线程signal时,均会加入同步队列
final boolean isOnSyncQueue(Node node) {
//waitStatus为CONDITION,肯定不在同步队列,因为进入同步队列,ws改为0
// node.prev为null,也不可能在同步队列,因为同步队列中至少有头节点
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 有后继节点,肯定在同步队列中,因为仅在同步队列中,才会给node.next设值
if (node.next != null)
return true;
/*
* 如果以上条件都不满足,那么这个node可能正在通过CAS设置为同步队列tail节点
* 但是还没来得及设置next值,也有可能是CAS入队压根没成功,这个时候就需要从
* 同步队列尾部开始往前遍历,查找该node节点
*/
return findNodeFromTail(node);
}
二、signal()方法
调用该方法的线程可以去唤醒当前condition队列中第一个等待的线程
public final void signal() {
//当前线程要唤醒另一个线程,那么当前线程必须持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal(Node first)
方法用于在condition队列中,从队头开始寻找一个未被取消的node,进行唤醒。
private void doSignal(Node first) {
do {
//如果是 first.nextWaiter==null,说明队列是空的,那么直接将lastWaiter置空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//准备被唤醒的node先出队
first.nextWaiter = null;
//如果当前节点被取消,且后继节点仍存在,则一值循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal(Node node)
将node节点从condition队列移动到AQS阻塞队列,成功返回true。
final boolean transferForSignal(Node node) {
// 如果当前线程被取消,那么设置状态失败,返回false
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将当前node加入到同步队列尾部,并返回前驱节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前驱节点被取消(ws > 0)或前驱节点的等待状态未成功设未signal,那么立即唤醒当前线程
// 如果不唤醒,那么当前线程将无人去唤醒它去竞争锁
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
三、signalAll()方法
signalAll()方法用于将condition队列中所有node都转移到同步队列中。
这里分析其内部调用的doSignalAll(Node first)
方法。
private void doSignalAll(Node first) {
//移出整个condition队列中node,所以前后节点设为null
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
//挨个将node节点从condition队列移动到AQS阻塞队列,成功返回true。
transferForSignal(first);
first = next;
} while (first != null);
}
四、总结
1、await()方法的调用线程必须持有锁,如果该线程通过重入锁,多次获取了锁,那么调用await(),会抛出异常IllegalMonitorStateException
,并且它在condition队列中的状态设为取消状态(CANCELLED
),线程并不会被挂起。
2、signal()方法调用者必须持有锁,该方法并不能保证唤醒当前线程,只是将当前线程从condition队列转移到同步队列,仅当它在同步队列中的前驱节点被取消,或者等待状态未成功设置未signal状态,才会立即取消当前node的park状态。