多线程编程中,为了提高并发性,往往将一个任务分解为不同的部分。将其交由不同的线程来执行。这些线程间相互协作时,仍然可能会出现一个线程等待另一个线程完成一定的操作,其自身才能继续运行的情形。
保护性暂挂模式(Guarded Suspension)可以帮助我们解决上述的等待问题。该模式的核心思想是如果某个线程执行特定的操作前需要满足一定的条件,则在该条件未满足时将该线程暂停运行(即暂挂线程,使其处于waiting状态,直到该条件满足时才继续运行)。wait/notify可以用来实现保护性暂挂模式,但是,该模式还要解决wait/notify所解决的问题之外的问题。
保护性暂挂模式的 核心是一个受保护的方法,该方法执行其所要真正执行的操作时需要满足特定的条件(Predicate),当条件不满足时,执行受保护方法的线程会被挂起进入等待状态,直到该条件满足时线程才会继续运行。 其类图如下所示:
GuardedObject:包含受保护方法的对象,其主要方法及职责如下:
GuardedAction:抽象了目标动作(受保护方法所要执行的操作),关联了目标动作所需的保护条件。其主要方法及职责如下
ConcreteGuardedAction:应用程序所实现的具体目标动作极其关联的保护条件。
Predicate:抽象了保护条件
ConcretePredicate:应用程序所实现的具体保护条件。
Blocker:负责对执行guardedMethod的线程进行挂起和唤醒,并执行ConcreteGuardedAction所实现的目标操作,其方法及职责如下:
ConditionVarBlocker:基于Java条件变量(java.util.concurrent.locks.Condition)实现的Blocker。
其时序图如下
代码示例:
// 1. sendAlarm是一个受保护方法
public void sendAlarm(final AlarmInfo alarm) throws Exception {
// 2. 创建guardedAction实例
GuardedAction<Void> guardedAction =
new GuardedAction<Void>(agentConnected) {
public Void call() throws Exception {
doSendAlarm(alarm);
return null;
}
};
// 3. 调用Blocker实例的callWithGuard方法。
blocker.callWithGuard(guardedAction);
}
public <V> V callWithGuard(GuardedAction<V> guardedAction) throws Exception {
lock.lockInterruptibly();
V result;
try {
// 4-5. callWithGuard方法调用guardedAction的getGuard方法获取保护条件Predicate。
final Predicate guard = guardedAction.guard;
while (!guard.evaluate()) {
//6-8:循环判断保护条件是否成立
Debug.info("waiting...");
condition.await();
}
// 9-10 callWithGuard方法调用guardedAction的call方法来执行目标动作,并记录call方法的返回值。
result = guardedAction.call();
return result;
} finally {
lock.unlock();
}
}
受保护方法的执行线程被暂挂后,当保护条件成立时,其他线程需要唤醒该线程.其序列图如下
代码示例:
protected void onConnected() {
try {
//2-3.创建stateOperation,调用Blocker实例的signalAfter方法
blocker.signalAfter(new Callable<Boolean>() {
@Override
public Boolean call() {
connectedToServer = true;
Debug.info("connected to server");
return Boolean.TRUE;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public void signalAfter(Callable<Boolean> stateOperation) throws Exception {
lock.lockInterruptibly();
try {
//4-5 signalAfter方法调用stateOperation对象的call方法以改变GuardedObject实例的状态
if (stateOperation.call()) {
//调用java.util.concurrent.locks.Condition实例的signal方法唤醒被该Condition实例所暂挂的线程中的一个线程。
condition.signal();
}
} finally {
lock.unlock();
}
}
关注点分离,保护性暂挂模式中的各个参与者各自仅关注本模式所要解决的问题中的一个方面,各个参与者的职责是高度内聚的。这使得保护性暂挂模式便于理解和应用,应用开发人员只需要根据应用的需要实现GuardedObject、ConcretePredicate、和ConcreteGuardedAction这几个必须由应用实现的参与者即可,而其他参与者的实现都是可复用的。
可能增加JVM垃圾回收的负担,为了使GuardedAction实例的call方法能够访问保护方法guardedMethod参数,我们需要利用闭包。因此,GuardedAction实例可能是在保护方法中创建的,这意味着,每次保护方法被调用的时候都会有个新的GuardedAction实例被创建。而这会增加JVM内存池的占用,从而增加垃圾回收的负担。
可能增加上下文切换,这点与保护性暂挂模式本身无关。只不过,不管如何实现该模式,只要这里面涉及线程的暂挂和唤醒就会引起上下文切换。如果频繁出现保护方法被调用时保护条件不成立,那么保护方法的执行线程就会频繁的被暂挂和唤醒,从而导致频繁的上下文切换。