十六、condition之signal方法

68 阅读2分钟

signal方法

signal方法只会唤醒condition链表中一个线程,从链表的头节点开始。如果节点唤醒失败,尝试唤醒下一个节点线程,直到成功唤醒某一线程。其执行流程:

  1. 判断当前线程是否持有锁资源,如果没有抛出异常
  2. 从单向链表的头节点开始遍历,找到第一个可以将状态从-2改成0的节点
  3. 将节点脱离Condition单向链表
  4. 将Node状态从-2改成0
  5. 将Node节点添加到AQS双向链表中
  6. 判断添加到AQS双向链表中的节点的前一个节点的waitStatus是否大于0,如果是,说明此节点是无效节点。当前节点排在此节点后面,永远不会被唤醒。所以在这种情况下,需要唤醒当前节点的线程。因为在await方法中,线程从挂起处被唤醒后,会去执行acquireQueued方法,尝试获取锁资源,如果获取失败,清除AQS队列中的无效节点,将当前节点排到有效节点后面。
  7. 除了上述的情况外,还有一种情况会使得当前节点添加到AQS队列后无法被唤醒,就是当前节点刚被添加到AQS队列后,其前一个节点就被标识为无效节点。为了避免这种情况,需要在判断前一个节点是有效节点后,再判断通过CAS将前一个节点状态改成-1是否成功,如果不成功,说明前一个节点的状态发生了改变,此时也需要唤醒当前节点的线程。
  8. 如果当前节点添加到AQS后,前一个节点的状态小于等于0,并且可以将状态改成-1,那么就不需要在signal方法中唤醒当前节点的线程。
public final void signal() {
	// 判断当前线程是否持有锁资源,如果没有抛出异常
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	Node first = firstWaiter;
	if (first != null)
		doSignal(first);
}
private void doSignal(Node first) {
	do {
		if ( (firstWaiter = first.nextWaiter) == null)
			// 链表中只有一个节点
			lastWaiter = null;
		first.nextWaiter = null;
	} while (!transferForSignal(first) &&
			 (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
	// 将Node的状态从-2改成0
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
		return false;

	// 将Node添加到AQS队列中,并且返回前一个节点
	Node p = enq(node);
	int ws = p.waitStatus;
	// 如果前一个节点的状态为1,表示是个要取消的节点,则立马唤醒当前节点,基于acquireQueued方法,
	// 当前节点会找一个正常的prev节点,并且挂起当前节点
	// 如果prev节点正常,但是CAS修改prev节点状态失败了。证明prev节点因为并发原因导致状态改变。
	// 为了避免当前节点无法被正常唤醒,还是要立即唤醒当前节点,让其找到一个正常的prev节点
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		LockSupport.unpark(node.thread);
	return true;
}