并发设计模式——保护性暂停(Guarded Suspension)
什么叫做 Guarded Suspension ?
guarded 的意思是”守护、担保“,suspension的意思是”悬挂、停止“,连起来的意思就是”受保护的暂停“。该模式常用在线程访问某个对象时,发现条件不满足,于是便暂时挂起等待条件满足再发起访问。
Guarded Suspension 的应用场景
在并发设计中,很多设计场景都有这种设计模式的影子,比如:
- Java中的join、Future、FutureTask
- 生产消费模式
- Worker Thread设计模式
- Java并发包中的 ArrayBlockingQueue
等等都使用了这种设计模式。
Guarded Suspension 源码展示
join()
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
当调用无参join()方法时,就是形参millis = 0,从上面源码可以看出,当millis = 0时,调用线程开始了死等。等待正在执行的线程执行结束。
在Java源码中java.util.concurrent.ArrayBlockingQueue类中有两个方法是值得关注的:
put()
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
当Queue容量达到上限时,一个线程调用put() 方法便陷入了无时限的等待,直到Queue容量不再满。
当Queue容量为0时,一个线程调用take() 方法也会陷入无时限的等待,直到Queue容量不为0。
Guarded Suspension 自定义实现
根据Java提供的源码,我们可以自定义设计出一个和 Guarded Suspension 模式有关的项目案例:
在一家餐馆里,来了一位客人点餐,客人点完餐后,厨师就开始做菜,在厨师做菜期间,服务员会根据餐盘中是否有菜判断菜是不是做好了,如果做好了,就去给客户送餐。
代码实现如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
log.debug("客户开始点餐");
//str1 str2 作为做菜的材料
String str1 = "鱼香";
String str2 = "肉丝";
Object lock = new Object();
//str3作为餐盘
final String[] str3 = {null};
new Thread(() -> {
synchronized (lock) {
log.debug("厨师开始做菜");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("厨师把菜做好了, 并装入了盘中");
str3[0] = str1 + str2;
lock.notify();
}
}, "厨师").start();
Thread.sleep(200);
new Thread(() -> {
log.debug("服务员开始尝试获取鱼香肉丝");
//如果餐盘为空,则表示没有做好
if (str3[0] == null) {
log.debug("厨师还没做好,获取失败");
log.debug("开始等待厨师做菜");
synchronized (lock) {
}
}
log.debug("服务员成功获取到了菜");
}, "服务员").start();
}
}
根据代码的运行结果可以看出,厨师开始做菜后,服务员尝试获取,但发现条件不满足后,便开始了无时限的等待,直到厨师做好了菜,这就是典型的保护性暂停案例。
除了一对一以外,还有一对多,多对多的情况,线程池便是运用了多对多的情况。
并且针对以上代码,我们还可以根据情况给出相应的优化,比如有时限的等待,如果一段时间内未获取到满足的条件,便放弃本次获取等等。
总结
相对来说Guarded Suspension设计模式并不是一个复杂的模式而是比较简单的一个模式,从上面例子中可以看出使用也相对比较简单。Guarded Suspension 的关注点还是在于临界值的是否满足条件。当达到设置的临界值是相关线程会被挂起。在很多的设计场景中都有这种设计模式的影子。
相对来说Guarded Suspension设计模式并不是一个复杂的模式而是比较简单的一个模式,从上面例子中可以看出使用也相对比较简单。Guarded Suspension 的关注点还是在于临界值的是否满足条件。当达到设置的临界值是相关线程会被挂起。在很多的设计场景中都有这种设计模式的影子。