【JAVA核心知识】22:从源码看ReentrantLock的Condition

22-F.1:Condition的简单使用介绍了Condition的简单使用及常用方法,Condition在JDK 1.5引入与ReentrantLock结合使用可以对线程进行主动控制,相较于Object的wait(),notify()方法,Condition具有更全面的功能。
可以通过ReentrantLock.newCondition()来获得一个Condition实例,Condition与ReentrantLock绑定,持有绑定ReentrantLock锁的线程才能通过Condition进行阻塞和唤醒。不同于synchronized使用lockObj的wait(),notify()方法仅有一个等待队列,一个ReentrantLock可以绑定有多个Condition,即可以设置多个等待队列。一个Condition代表一个通信通道,但是要注意每个Condition的等待指令只能由同一个Condition唤醒。不同Condition之间是无法交叉唤醒的。
下面从源码的角度来看Condition是如何实现的,如果在此之前对ReentrantLock的实现有了解话下面的内容会更容易理解。

1 常用方法

  • await():阻塞等待
  • await(long time, TimeUnit unit):阻塞等待指定时间,指定时间后自动唤醒开始竞争锁
  • awaitNanos(long nanosTimeout): 纳秒级别的阻塞等待指定时间,指定时间后自动唤醒开始竞争锁
  • awaitUntil(Date deadline):阻塞等待到某一日期,到达deadline时自动唤醒开始竞争锁
  • awaitUninterruptibly():无中断的等待,在这个等待状态下,此线程会无视对他的中断指令
  • signal():唤醒一个阻塞的线程
  • signalAll():唤醒所有阻塞的线程,等效于notifyAll()

注意Condition也是有wait()和notify()方法的,这是因为wait()和notify()是属于Object的,而任何类都继承了Object。但是wait()和notify()并不属于Condition提供的功能,将Condition作为一个普通的锁对象使用synchronized时他们才有用。因此切记不要用await()等待了却想用notify()唤醒,是唤醒不了的,他们本身就不是一套。await和signal结合使用,wait和notify结合使用。

2 定义

源码:

public interface Condition {

    void await() throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

解析: Condition是接口,定义了应拥有的功能,那么ReentrantLock.newCondition()返回的是谁呢。

3 构建

源码:

ReentrantLock:
public Condition newCondition() {
    return sync.newCondition(); // 调用sync的
}
Syncfinal ConditionObject newCondition() {
    return new ConditionObject();
}

解析: ReentrantLock.newCondition()返回的是一个ConditionObject对象。ConditionObject是AbstractQueuedSynchronizer的一个内部类。

4 关键属性

  • private transient Node firstWaiter;:等待队列的头部
  • private transient Node lastWaiter;:等待队列的尾部

5 await()

源码:

public final void await() throws InterruptedException {
	// await()关注中断,所以检查一下线程是否中断
	if (Thread.interrupted())
		throw new InterruptedException();
	// 新增一个属于当前线程的等待节点到等待队列中去
	// 见5-M.1 ConditionObject.addConditionWaiter()
	Node node = addConditionWaiter();
	// 释放锁并获取当前状态
	// 见5-M.2 AbstractQueuedSynchronizer.fullyRelease(Node node)
	int savedState = fullyRelease(node);
	int interruptMode = 0;
	// 自旋判断node节点在不在同步队列中,在的话说明已经被唤醒,等待获取锁,不在的话说明还在阻塞中没被signal
	// 见5-M.3 AbstractQueuedSynchronizer.isOnSyncQueue(Node node)
	while (!isOnSyncQueue(node)) {
	 	// 没在等待队列的话就阻塞
		LockSupport.park(this);
		/* 检查中断,共有三个返回值 THROW_IE(-1),0,REINTERRUPT(1),
		 * 其中0表示线程没有执行中断。不等于0就是说线程被中断了,而wait是关注中断的,所以这里直接跳出循环。
		 * 三个返回值的意义见5-M.4 ConditionObject.checkInterruptWhileWaiting(Node node)
		*/
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
			break;
	}
	/*
	* 走到这里说明线程已被某种原因唤醒了,开始竞争锁。无论你是中断唤醒还是正常signal,要执行相应动作都要拿到锁再执行
	* acquireQueued竞争锁并返回竞争过程中的中断标识,竞争锁竞争状态也是之前的状态savedState,这样才能完全复原await之前的加锁状态,才可以应对重入锁场景
	* acquireQueued见5-M.5 AbstractQueuedSynchronizer.acquireQueued(final Node node, int arg)
	* interruptMode != THROW_IE表示当前不是THROW_IE模式(也就是要抛出InterruptedException的模式)。
	* 这个if内的条件意思就是如果竞争锁过程中发生了中断,但是之前的模式不是抛出异常,那么就把模式设置为REINTERRUPT以便后面复原中断标识
	* acquireQueued方法要求node在同步队里中,node从等待队列到同步队列中,一
	* 是signal时间被移入的,其中状态是0和REINTERRUPT的线程都是走的这种途径,
	* 但是状态是THROW_IE是在signal之前中断导致的跳出,这种线程走的是上面
	* checkInterruptWhileWaiting方法里的transferAfterCancelledWait,
	* 里面会调用enq(node)把node放入同步队列,transferAfterCancelledWaitb保证执行完毕时节点一定已经进入同步队列
	*/
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		interruptMode = REINTERRUPT;
	if (node.nextWaiter != null) // clean up if cancelled
		 // 如果等待队列里面还有后续节点,就把队列里所有不是CONDITION状态的节点去不清理掉,这个方法仅被持有锁的线程调用
		 // unlinkCancelledWaiters见5-M.1.1 ConditionObject.unlinkCancelledWaiters()
		unlinkCancelledWaiters();
	if (interruptMode != 0)
		// 如果是有中断的模式,那么需要根据模式做相应的操作(抛异常还是复原中断标识),THROW_IE抛异常,REINTERRUPT复原中断标识
		// 见5-M.6 ConditionObject.reportInterruptAfterWait(int interruptMode)
		reportInterruptAfterWait(interruptMode);
}

解析: Condition会维护一个自己等待队列,执行await()时会为当前线程产生一个节点并加入等待队列,通过节点是否已经移入同步队列来判断是否已被signal唤醒。对于线程中断,设置了三种模式对应不同的场景(无中断,复原中断标识,抛中断异常)。而且每次await()结束之前都会对整个等待队列内的所有无效节点进行一次清理,这一点和ReentrantLock不同,ReentrantLock是发现无效节点时才执行清理,而且仅清理与发现的无效节点相连的无效节点。
5-M.1 ConditionObject.addConditionWaiter()
源码:

AbstractQueuedSynchronizer$ConditionObject:
private Node addConditionWaiter() {
	Node t = lastWaiter;
	// If lastWaiter is cancelled, clean out.
	// 如果队列尾部是一个无效节点,那么进行队列清理
	if (t != null && t.waitStatus != Node.CONDITION) {
		// 清理所有的无效节点,见5-M.1.1 ConditionObject.unlinkCancelledWaiters()
		unlinkCancelledWaiters();
		// 清理后重新赋值t
		t = lastWaiter;
	}
	// 构造一个CONDITION状态的节点
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	/* 将节点载入等待队列,这里没有用CAS保证同步,且await()对当前线程是否持有锁的
	* 验证在下一步5-M.2 AbstractQueuedSynchronizer.fullyRelease(Node node),
	* 所以这个方法严格来说不能保证并发,因此我们要保证持有锁的线程线程才调用
	* await(),用锁保证同步。我个人觉得这是一个bug
	*/
	if (t == null)
		firstWaiter = node;
	else
		t.nextWaiter = node;
	lastWaiter = node; // 设置尾部
	return node;
}

解析: 将当前线程放入等待队列的尾部。此方法并不能保证同步,且验证当前线程持有锁的操作在下一步,所以存在一定的隐患:如果持有锁的线程和未持有锁的线程并发执行可能导致持有锁的线程节点被覆盖,且清理方法unlinkCancelledWaiters也可能因未保证同步产生预期之外的结果。另外这个方法无论当前线程是否持有锁都会构造一个节点加入等待队列,因此后续存在操作验证当前线程是否持有锁,如果不持有锁,那么会把这个节点置为CANCELLED无效状态,以进行清除。不过更好的方式不应该是在刚进入await()就进行判断吗?就像8 signal() 一样。 此方法确实存在bug,在后续的版本中已经被修复,bugId: JDK-8187408. 详见:给JDK提的一个bug(关于AbstractQueuedSynchronizer.ConditionObject)
5-M.1.1 ConditionObject.unlinkCancelledWaiters()
源码:

private void unlinkCancelledWaiters() {
	Node t = firstWaiter; // 等待队列首部
	Node trail = null;
	while (t != null) {
		Node next = t.nextWaiter; // 先记录一下next
		if (t.waitStatus != Node.CONDITION) { // 当前节点是一个无效节点的话
			t.nextWaiter = null; //断开对next的引用
			if (trail == null)
			 	// trail是null的话说明还没有碰到有效节点,暂时把next设置成firstWaiter,这并不代表着next一定是有效节点,如果next也是无效的,那么验证next时trail还是null,还是会被继续覆盖
				firstWaiter = next;
			else
				// 如果已有有效节点的话就继续往后挂
				trail.nextWaiter = next;
			if (next == null) // 没有后续的话赋值lastWaiter 
				lastWaiter = trail;
		}
		else
			trail = t; // 碰到有效节点的话就记录一下
		t = next;
	}
}

解析: 清理等待队列里的所有无效节点。
5-M.2 AbstractQueuedSynchronizer.fullyRelease(Node node)
源码:

final int fullyRelease(Node node) {
	boolean failed = true;
	try {
		// 当前的锁状态,其实也就是锁重入次数
		int savedState = getState();
		// 完全释放锁并唤醒下一个线程
		// 因为是await()要进入阻塞状态,所以要一次性释放锁-savedState
		// 见5-M.2.1 AbstractQueuedSynchronizer.release(int arg)
		if (release(savedState)) { // 如果当前线程不是持有锁的线程,这里会抛异常
			failed = false;
			return savedState;
		} else { // 如果当前线程没有完全释放锁,要抛出对应的异常
			throw new IllegalMonitorStateException();
		}
	} finally {
		if (failed) // 如果释放失败了,那么表示当前线程没有持有锁,那么就需要把当前节点改为CANCELLED。因为上一步已经把节点放入等待队列了,现在这个节点又不符合条件,所以就要把这个节点置为无效
			node.waitStatus = Node.CANCELLED;
	}
}

解析: 完全释放锁并顺序唤醒同步队列中的下一个等待节点。
5-M.2.1 AbstractQueuedSynchronizer.release(int arg)
此方法及后续的解析可见21:从源码看ReentrantLock4.4-M.1 AbstractQueuedSynchronizer.release(int arg),这里不在重复描述。这个方法操作有两个,一是释放指定的锁资源,二是如果同步队列有等待唤醒的节点的话就顺序唤醒下一个等待节点。当然执行这两个操作之前会校验持有锁的线程是否为当前线程,否则就会抛出IllegalMonitorStateException。
5-M.3 AbstractQueuedSynchronizer.isOnSyncQueue(Node node)
源码:

final boolean isOnSyncQueue(Node node) {
	// 节点状态是CONDITION 或者节点的prev为空(便是还没挂上去),那就是肯定没进入同步队列
	if (node.waitStatus == Node.CONDITION || node.prev == null)
		return false;
	// 节点的next有值,那肯定已经在同步队列了,因为等待队列使用nextWaiter做的链接,next是同步队列用的
	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.
	 */
	 // 即使node.prev不为空依然不能保证一定会在同步队列中,这是因为先设置prev再CAS设置tail的,CAS失败获得执行都是node.prev不为但是实际并不在同步队列的情况,此时通过tail(tail设置通过CAS完成,肯定在链上)往前遍历同步节点,看节点是否存在。而从tail往前而不是head往后的好处是因为作为一个新晋节点肯定是在尾部附近,如果真的在队列中可以很快的找到结束遍历,节省性能。
	return findNodeFromTail(node);
}

解析: 查看目标节点是否在同步队列中,在的话返回true。
5-M.3.1 AbstractQueuedSynchronizer.findNodeFromTail(Node node)
源码:

private boolean findNodeFromTail(Node node) {
	Node t = tail;
	for (;;) {
		if (t == node)
			return true;
		if (t == null)
			return false;
		t = t.prev;
	}
}

解析: 平平无奇,从tail开始往前遍历,查询目标节点是否在同步队列中。在返回true,不在返回false.
5-M.4 ConditionObject.checkInterruptWhileWaiting(Node node)
源码:

private int checkInterruptWhileWaiting(Node node) {
	// 检查并重置线程中断标识,使用interrupted()重置中断标识是很有必要的,详情见解析
	return Thread.interrupted() ?
		// 判断线程是在signal前还是signal后,前的话返回THROW_IE状态 ,后的话返回REINTERRUPT状态,同时也确保节点已进入同步队列
		// transferAfterCancelledWait见5-M.4.1 AbstractQueuedSynchronizer.transferAfterCancelledWait(Node node)
		(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
		0;
}

解析: 检查线程的中断标识并返回对应的状态。如果线程未执行中断则返回状态0,在signal前执行了中断则返回THROW_IE 状态,signal后执行了中断则返回REINTERRUPT状态。三个返回值THROW_IE(-1),0,REINTERRUPT(1)中,0表示线程没有执行中断,THROW_IE(-1):表示线程在被signal之前执行了中断,线程是被中断唤醒的。这是因为interrupt()也会唤醒park的线程,且线程中断标识为true时后续执行park是无效的,REINTERRUPT(1):标识线程在被signal之后执行了中断。 检查中断使用使用interrupted()而不是获取当前线程再使用isInterrupted()是很有必要的,因为中断标识为true时会使得park失效无法进行阻塞,因此这里使用interrupted()重置中断标识
5-M.4.1 AbstractQueuedSynchronizer.transferAfterCancelledWait(Node node)
源码:

final boolean transferAfterCancelledWait(Node node) {
	// CAS修改node的状态,如果修改成功说明node处于CONDITION状态,那就是signal前的中断
	if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
		// 还得把这个节点放到同步队列中去
		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.
	 */
	 /*
	 * isOnSyncQueue见5-M.3 AbstractQueuedSynchronizer.isOnSyncQueue(Node node),
	 *  判断节点是否在同步队列中。因为上面判断不是CONDITION状态说明线程已经被signal了。
	 * 这里自旋的原因是确保signal的那个线程已经把node放入同步队列了。而不是只修改了状态还在放,避免可能出现的并发问题
	 */
	while (!isOnSyncQueue(node))
		Thread.yield();
	// 返回false标识在signal后的中断
	return false;
}

解析: 检查中断是发生在signal前还是signal后。signal前的话会用enq方法是把节点放入同步队列中。如有兴趣可查看21:从源码看ReentrantLock4.2-M.1.1.2.1 AbstractQueuedSynchronizer.enq(final Node node) 。signal后的话会自旋等到确保节点已进入同步队列才返回。因此这个方法保证执行完毕后节点已经在同步队列中。
5-M.5 AbstractQueuedSynchronizer.acquireQueued(final Node node, int arg)
解析: acquireQueued就是一个竞争锁并返回竞争过程中的中断标识的方法。acquireQueued方法要求node在同步队里中,node从等待队列到同步队列中,一是signal时间被移入的,其中状态是0和REINTERRUPT的线程都是走的这种途径,但是状态是THROW_IE是在signal之前中断导致的跳出,这种线程走的是上面checkInterruptWhileWaiting方法里的5-M.4.1 AbstractQueuedSynchronizer.transferAfterCancelledWait(Node node),里面会调用enq(node)把node放入同步队列。这个方法的详细解析见21:从源码看ReentrantLock的4.2-M.1.1.3 AbstractQueuedSynchronizer.acquireQueued(final Node node, int arg)。涉及了锁的获取顺序和无效节点清除等与锁相关的逻辑。
5-M.6 ConditionObject.reportInterruptAfterWait(int interruptMode)
源码:

private void reportInterruptAfterWait(int interruptMode)
	throws InterruptedException {
	// THROW_IE抛异常
	if (interruptMode == THROW_IE)
		throw new InterruptedException();
	else if (interruptMode == REINTERRUPT)
	// REINTERRUPT复原中断标识
		selfInterrupt(); // 这个方法里面就是一行Thread.currentThread().interrupt();
}

解析: 根据interruptMode判断中断状态,要记得一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。因此所谓的线程中断仅仅是设置线程的中断标识,至于是否响应这个中断,由线程自己判定中断标识来自己决定。而await()是考虑中断的,因此对于park过程中发生中断而返回的THROW_IE状态需要抛出异常,但是park过程中已经不再持有锁,不能直接抛异常出去,因此就记录了这个状态,等到拿到锁之后,走到这里判断出来是THROW_IE状态,就抛出一个异常就是告诉调用方,这个线程在wait过程中发生中断了,具体的处理逻辑交于调用方处理。而REINTERRUPT状态则表示中断发生在signal之后,此时线程已脱离park状态了,而上面5-M.4 ConditionObject.checkInterruptWhileWaiting(Node node) 中使用的Thread.interrupted() 会使中断标识重重,所以对于REINTERRUPT则会在这里复原中断标识位,同样将对中断逻辑的处理交于调用方处理。

6 await(long time, TimeUnit unit),awaitNanos(long nanosTimeout)和awaitUntil(Date deadline)

这三个方法逻辑都是一致的,只不过是提供了不同方式传入超时时间的方法,这里就仅以await(long time, TimeUnit unit)为例看一下这种带超时时间的await是如何实现的。
源码:

public final boolean await(long time, TimeUnit unit)
		throws InterruptedException {
	// 将时间转换成纳秒
	long nanosTimeout = unit.toNanos(time);
	// 关注中断,所以检查一下线程是否中断
	if (Thread.interrupted())
		throw new InterruptedException();
	// 新增一个属于当前线程的等待节点到等待队列中去
	// 见5-M.1 ConditionObject.addConditionWaiter()
	Node node = addConditionWaiter();
	// 释放锁并获取当前状态
	// 见5-M.2 AbstractQueuedSynchronizer.fullyRelease(Node node)
	int savedState = fullyRelease(node);
	// 计算等待的终止时间
	final long deadline = System.nanoTime() + nanosTimeout;
	// 超时标识
	boolean timedout = false;
	int interruptMode = 0;
	// 自旋判断node节点在不在同步队列中,在的话说明已经被唤醒,等待获取锁,不在的话说明还在阻塞中没被signal
	// 见5-M.3 AbstractQueuedSynchronizer.isOnSyncQueue(Node node)
	while (!isOnSyncQueue(node)) {
		// 如果已经到终止时间了
		if (nanosTimeout <= 0L) {
			// 获得线程是否已经被signal,也就是是否超时,通过transferAfterCancelledWait也保证执行完毕时节点一定已经进入同步队列
			// transferAfterCancelledWait见5-M.4.1 AbstractQueuedSynchronizer.transferAfterCancelledWait(Node node)
			timedout = transferAfterCancelledWait(node);
			break;
		}
		/*
		剩余时间是否大于自旋优化阈值,spinForTimeoutThreshold是1000L,
		也就是1000纳秒,这个判断是因为阻塞和唤醒都需要线程状态改变,
		很耗时,如果剩余时间过短,就没必要阻塞了,直接自旋就可以了,这个在ReentrantLock中可以见到
		*/
		if (nanosTimeout >= spinForTimeoutThreshold)
			// 阻塞指定的时间
			LockSupport.parkNanos(this, nanosTimeout);
		/* 检查中断,共有三个返回值 THROW_IE(-1),0,REINTERRUPT(1),
		 * 其中0表示线程没有执行中断。不等于0就是说线程被中断了,而wait是关注中断的,所以这里直接跳出循环。
		 * 三个返回值的意义见5-M.4 ConditionObject.checkInterruptWhileWaiting(Node node)
		*/
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
			// 由中断引起的唤醒的话就跳出自旋
			break;
		// 重新计算超时时间
		nanosTimeout = deadline - System.nanoTime();
	}
	/*
	* 走到这里说明线程已被某种原因唤醒了,开始竞争锁。无论你是中断唤醒还是正常signal,要执行相应动作都要拿到锁再执行
	* acquireQueued竞争锁并返回竞争过程中的中断标识,竞争锁竞争状态也是之前的状态savedState,这样才能完全复原await之前的加锁状态,才可以应对重入锁场景
	* acquireQueued见5-M.5 AbstractQueuedSynchronizer.acquireQueued(final Node node, int arg)
	* interruptMode != THROW_IE表示当前不是THROW_IE模式(也就是要抛出InterruptedException的模式)。
	* 这个if内的条件意思就是如果竞争锁过程中发生了中断,但是之前的模式不是抛出异常,那么就把模式设置为REINTERRUPT以便后面复原中断标识
	* acquireQueued方法要求node在同步队里中,node从等待队列到同步队列中,一
	* 是signal时间被移入的,其中状态是0和REINTERRUPT的线程都是走的这种途径,
	* 但是状态是THROW_IE是在signal之前中断导致的跳出,这种线程走的是上面
	* checkInterruptWhileWaiting方法里的transferAfterCancelledWait,
	* 里面会调用enq(node)把node放入同步队列
	*/
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		interruptMode = REINTERRUPT;
	if (node.nextWaiter != null) // clean up if cancelled
		 // 如果等待队列里面还有后续节点,就把队列里所有不是CONDITION状态的节点去不清理掉,这个方法仅被持有锁的线程调用
		 // unlinkCancelledWaiters见5-M.1.1 ConditionObject.unlinkCancelledWaiters()
		unlinkCancelledWaiters();
	if (interruptMode != 0)
		// 如果是有中断的模式,那么需要根据模式做相应的操作(抛异常还是复原中断标识),THROW_IE抛异常,REINTERRUPT复原中断标识
		// 见5-M.6 ConditionObject.reportInterruptAfterWait(int interruptMode)
		reportInterruptAfterWait(interruptMode);
	// 超时取反返回,这是因为await(long time, TimeUnit unit)正常signal返回的是true,那就是没超时
	return !timedout;
}

解析: 整体逻辑和5 await() 没什么区别,无外乎加了一个超时时间和自旋超时阈值。awaitNanos(long nanosTimeout)和awaitUntil(Date deadline)也都一样,不过是计算deadline的时间不一样。

7 awaitUninterruptibly()

源码:

public final void awaitUninterruptibly() {
	// 新增一个属于当前线程的等待节点到等待队列中去
	// 见5-M.1 ConditionObject.addConditionWaiter()
	Node node = addConditionWaiter();
	// 释放锁并获取当前状态
	// 见5-M.2 AbstractQueuedSynchronizer.fullyRelease(Node node)
	int savedState = fullyRelease(node);
	boolean interrupted = false;
	// 自旋判断node节点在不在同步队列中,在的话说明已经被唤醒,等待获取锁,不在的话说明还在阻塞中没被signal
	// 见5-M.3 AbstractQueuedSynchronizer.isOnSyncQueue(Node node)
	while (!isOnSyncQueue(node)) {
		// park进行休眠
		LockSupport.park(this);
		/*
		* 如果是中断引起的话就记录一个中断标记
		* 从这里就可以看到awaitUninterruptibly是无视中断的,中断来了无外乎做一个
		* 标记以复原中断标识,我该休眠还是继续休眠,使用interrupted()而不是获取当
		* 前线程再使用isInterrupted()是很有必要的,因为中断标识为true时会使得
		* park失效无法进行阻塞,因此这里使用interrupted()重置中断标识
		*/
		if (Thread.interrupted())
			interrupted = true;
	}
	/* 
	* 对于awaitUninterruptibly来说走到这里肯定是已经被signal了,开始竞争锁。
	* acquireQueued竞争锁并返回竞争过程中的中断标识,竞争锁竞争状态也是之前的状态
	* savedState,这样才能完全复原await之前的加锁状态,才可以应对重入锁场景
	* 然后无论是acquireQueued竞争锁过程中进行了中断导致返回了true,还是await时执行了中断导致interrupted为true,都要进行中断标识的复原
	*/
	if (acquireQueued(node, savedState) || interrupted)
		selfInterrupt();
}

解析: 了解5 await() 的话这个方法就更容易理解了,逻辑也相对简单。一个无视中断的await,会坚持await直到被signal。

8 signal()

源码:

public final void signal() {
	// 判断当初线程是不是持有锁的线程,不是的话就抛异常
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	// 获取等待队列头部
	Node first = firstWaiter;
	// 如果存在等待队列头部,就唤醒头部
	if (first != null)
		doSignal(first);
}

解析: 将此条件等待队列中等待时间最长的线程(如果存在)从此条件的等待队列移动到锁的同步队列。
8-M.1 ConditionObject.doSignal(Node first)
源码:

private void doSignal(Node first) {
	do {
		// 设置新的等待队列头部
		if ( (firstWaiter = first.nextWaiter) == null)
			// 如果等待队列头部没有后续节点的话,也要把尾部置空
			lastWaiter = null;
		// 断开first在等待队列的链接,以帮助GC
		first.nextWaiter = null;
	} while (!transferForSignal(first) &&	// 移动节点到锁的同步队列去,见8-M.1.1 AbstractQueuedSynchronizer.transferForSignal(Node node)
			 (first = firstWaiter) != null);	// 如果移动失败,比如节点别同步中断了,就继续往后找
}

解析: 一直自旋,直到有节点被成功移入锁的同步队列,或者此条件不存在等待节点。 自旋的原因是避免同步中断导致的节点失效。
8-M.1.1 AbstractQueuedSynchronizer.transferForSignal(Node node)
源码:

final boolean transferForSignal(Node node) {
	/*
	 * If cannot change waitStatus, the node has been cancelled.
	 */
	 // 如果中断导致的节点已失效,那么就会CAS失败,返回移动失败
	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).
	 */
	 // 将节点移入同步队列,并且返回他的prev节点(即使这个节点是同步队列唯一的有效节点,他依然会有prev的,原因可以看ReentrantLock)
	Node p = enq(node);
	// 看prev的状态
	int ws = p.waitStatus;
	// 状态大于0的只有CANCELLED(1),或者prev修改为signal状态失败
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		// 唤醒node对应的线程,让他去竞争锁
		LockSupport.unpark(node.thread);
	return true; // 返回移动成功
}

解析: 首先判断节点的状态,因为是刚从此条件的等待队列移出的,那么正常状态肯定是CONDITION,如果不是CONDITION,那么肯定产生signal前的中断导致节点变成CANCELLED了,成为一个无效节点,此时返回移动失败以继续往下遍历有效节点。如果CAS成功将节点的状态从CONDITION改成0,那么说明这是一个有效节点,就通过enq方法把这个节点移入锁的同步队列中去,enq方法可查看21:从源码看ReentrantLock4.2-M.1.1.2.1 AbstractQueuedSynchronizer.enq(final Node node) 。此时enq返回的是移入节点node的prev,也就是node移入同步队列后他的前一个节点,这个prev不是为空,即使node是同步队列中的第一个有效节点prev也不会是空,为什么这样可以查看21:从源码看ReentrantLock中是怎么维护同步队列的。
如果prev的状态是CANCELLED,那么就唤醒node对应的线程进行锁竞争,这样就可以避免上一个节点就是之前持有锁的节点,因为中断成为了CANCELLED状态,然后执行unLock时间node节点还没进入同步队列,导致无线程竞争进入死锁。即使不是这样,也依然可以借助锁竞争时会清除当前节点到上一个有效节点间的无效节点的逻辑进行一次无效节点的清除。
如果不是CANCELLED状态,需要把prev的状态修改为SIGNAL,因为SIGNAL表示存在后续节点需要唤醒,这样当前面节点unLock时才会正常唤醒后续节点,如果修改为SIGNAL的CAS操作失败,也唤醒node对应的线程。这是因为这个CAS失败并不意味着出现了错误,也许prev只是处于一个中间的其他状态。但是在这里并不能确定prev的失败是哪种原因。因此这里直接唤醒node对应的线程,让其参与到竞争中,通过锁的竞争逻辑来保证符合预期。

9 signalAll()

源码:

public final void signalAll() {
	// 判断当初线程是不是持有锁的线程,不是的话就抛异常
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	// 获取等待队列头部
	Node first = firstWaiter;
	// 如果等待队列头部不为null,说明存在节点,就开始唤醒
	if (first != null)
		doSignalAll(first);
}

解析:8 signal() 一样,不过最后调用了doSignalAll。
9-M.1 ConditionObject.doSignalAll(Node first)
源码:

private void doSignalAll(Node first) {
	lastWaiter = firstWaiter = null;
	do {
		Node next = first.nextWaiter;
		first.nextWaiter = null;
		// 移动节点到同步队列中 见8-M.1.1 AbstractQueuedSynchronizer.transferForSignal(Node node)
		transferForSignal(first);
		first = next;
	} while (first != null); // 继续往后走
}

解析: 从这里可以看到signal()是移动一个就结束了,signalAll()则是循环当前所有的节点。
PS:
【JAVA核心知识】系列导航 [持续更新中…]
关联导航:22-F.1:Condition的简单使用
关联导航:21:从源码看ReentrantLock
欢迎关注…

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值