概述
ConditionObject是AQS中定义的内部类,实现了Condition接口,ConditionObject是基于Lock实现的,在其内部通过链表来维护等待队列(条件队列)。Contidion必须在lock的同步控制块中使用,调用Condition的signal方法并不代表线程可以马上执行,signal方法的作用是将线程所在的节点从等待队列中移除,然后加入到同步队列中,线程的执行始终都需要根据同步状态(即线程是否占有锁)。
为什么平时使用Condition都是在lock的同步控制块中?即为什么在平时使用Condition之前要获取锁。
因为我们在平时开发过程中生成Condition对象,都是通过显式锁的public方法newCondition实现的,newCondition方法不是静态方法必须通过具体实例对象调用,因此需要先获取锁对象。
思考:怎样做才能不再同步控制块中也能使用ConditionObject对象?
ConditionObject是抽象类AbstractQueuedSynchronizer里用public修饰的内部类,因此只能在AQS或者AQS子类内部通过new关键字生成ConditionObject的对象。编写一个继承AbstractQueuedSynchronizer的非抽象的子类A,利用子类A生成的对象,就能生成父类public修饰的ConditionObject类型的对象。调用方式:
Condition conditionObject = new A().new ConditionObject();
ConditionObject定义的变量
//条件(等待)队列的第一个节点
private transient Node firstWaiter;
//条件(等待)队列的最后一个节点
private transient Node lastWaiter;
Node类是在AQS中定义的双向节点对象,在AbstractQueuedSynchronizer中有详细的解析。
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是否在同步队列中
*
* 这里有个理解难点,为什么需要判断节点Node是否在同步队列中呢?
* 因为当线程调用signal或signalAll时,会从firstWaiter节点开始,
* 将节点依次从等待队列中移除,并通过enq方法重新添加到同步队列中
*
* 因此当其他线程调用signal或者signalAll方法时,该线程可能从条件(等待)队列中移除,并重新加入到同步队列中
* 1. 如果没有,则阻塞当前线程,同时调用checkInterruptWhileWaiting检测当前线程在等待过程中是否发生中断,
* 设置interruptMode表示中断状态。
* 2. 如果isOnSyncQueue方法判断出当前线程已经处于同步队列中了,则跳出while循环
*/
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//利用acquireQueued方法循环尝试获取同步状态(锁)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();//将等待队列中,不是Node.CONDITION状态的节点移除
if (interruptMode != 0)//判断中断状态,
reportInterruptAfterWait(interruptMode);
}
//将当前线程生成的节点加入到链表末尾,并返回该节点
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果最后一个节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();//这个方法就是将等待队列中不是Node.CONDTION状态的节点从链表中移除
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
说明:在调用await方法时是占有锁的,await方法就是将占有锁的当前线程从同步队列中移除,释放同步状态(锁),然后将当前线程所在的节点加入到条件队列中。
signal方法源码解析
signalAll方法与signal方法原理一样,只是针对的是等待队列中所有的节点。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);
}
//更新节点状态,并通过enq方法,将节点重新加入同步队列中
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
分析上面的源码,我们可以看出,其实signal方法的核心其实是doSignal和transferForSignal方法,doSignal的主要作用就是将条件(等待)队列中的头节点firstWaiter从队列中移除,transferForSignal方法的主要作用就是将doSignal方法中移除的firstWaiter节点通过enq方法重新添加到同步队列中,从这里也可以看出为什么会在await方法中调用isOnSyncQueue方法判断节点是否处于同步队列中了。
下面我们用图来表示await方法节点的存放过程:
由于await方法必须在占有锁后才能调用,因此,此时线程所在的节点是同步队列的头结点:
当成功调用了Condition的await方法,此时线程拿到了锁,接下来需要将当前线程的节点从同步队列中移除,在await方法内部的addConditionWaiter执行后,会新生成一个持有当前线程的节点加入到了等待队列中,即节点变化过程如下:
之后await方法会通过isOnSyncQueue方法判断,节点是否在同步队列中,至于为什么要判断,上面我已经做了解释,不再重复。signal和signalAll方法就是将条件队列中的节点按顺序移除,并重新添加到同步队列,下面我们就看看signal方法中,节点的变化过程:
节点变化总结,经过上面节点的变化过程,我们发现一个线程所在的节点从同步队列中移除加入到等待队列过后,可能会存在一种特殊情况就是其他线程通过signal方法又将该节点从等待队列中移除然后加入到同步队列中。经过上面的分析我们也可以看出await方法实际上就是在while循环中阻塞线程的。