文章导读:并发编程高级篇
(一)并发设计模式
多线程版本的 If
Guarded Suspension模式
Guarded Suspension模式也被叫做Guarded Wait 模式,我也更倾向这种叫法。实际上,本质就是是一种等待唤醒机制的实现。
我觉得这个模式比较经典的一个应用是异步转同步(比如Dubbo 中 DefaultFuture 这个类也是采用的这种方式)
又比如用户通过浏览器发过来一个请求,而服务调用方A接受到这个请求,而这个请求需要请求其它服务提供方B的某个接口来返回结果。然而这个服务B暴露出来的交互方式并不是Http的方式,而是通过MQ。所以服务调用方A会将这个请求转换成一个异步消息发送给 MQ,等 MQ 返回结果后,再将这个结果返回至浏览器。那服务A在发送MQ之后,如何等待返回结果呢?这些场景都可以通过Guarded Suspension模式来解决。
下图就是 Guarded Suspension 模式的结构图,非常简单,一个对象 GuardedObject,内部有一个成员变量——受保护的对象,以及两个成员方法——get(Predicate p)和onChanged(T obj)方法
GuardedObject 的内部实现非常简单,是管程的一个经典用法,你可以参考下面的示例代码,核心是:get() 方法通过条件变量的 await() 方法实现等待,onChanged() 方法通过条件变量的 signalAll() 方法实现唤醒功能。逻辑还是很简单的,所以这里就不再详细介绍了。
class GuardedObject<T>{
//受保护的对象
T obj;
final Lock lock =
new ReentrantLock();
final Condition done =
lock.newCondition();
final int timeout=1;
//获取受保护对象
T get(Predicate<T> p) {
lock.lock();
try {
//MESA管程推荐写法
while(!p.test(obj)){
done.await(timeout,
TimeUnit.SECONDS);
}
}catch(InterruptedException e){
throw new RuntimeException(e);
}finally{
lock.unlock();
}
//返回非空的受保护对象
return obj;
}
//事件通知方法
void onChanged(T obj) {
lock.lock();
try {
this.obj = obj;
done.signalAll();
} finally {
lock.unlock();
}
}
}
看代码的话,这不就是管程的等待唤醒机制吗。是的,只不过 Guarded Suspension 模式将其规范化了
怎么理解他是属于多线程版本的If呢?
在单线程中,if语句是不会使用等待的。因为在只有一个线程的条件下,如果这个线程被阻塞,那就没有其他活动线程了,这意味着 if 判断条件的结果也不会发生变化了。但是多线程场景中,等待就变得有意义了,这种场景下,if 判断条件的结果是可能发生变化的。所以,用“多线程版本的 if”来理解这个模式会更简单。
CompletableFuture.get(好像也是通过这种方式实现的)。我理解受保护对象就是返回结果。返回结果为空就等待,直到被唤醒
Balking模式
多线程版本的If中,Guarded Suspension模式是需要等待的,而且还很执着,必须要等到条件为真。但很显然这个世界,不是所有场景都需要这么执着,有时候我们还需要快速放弃。Balking模式也可以理解是多线程版本的If,Balking 模式是不会等待,条件不符合,直接就Pass了。
比如编辑器自动保存或者自动保存路由表功能等,这两个功能一般都会使用一个定时任务啥的去做,每隔几秒中,检查共享变量 changed是否发生改变…
Balking 模式有一个非常典型的应用场景就是单次初始化
class InitTest{
boolean inited = false;
synchronized void init(){
// 竞态条件
if(inited){
return;
}
//省略doInit的实现
doInit();
inited=true;
}
}
线程安全的单例模式本质上其实也是单次初始化,所以可以用 Balking 模式来实现线程安全的单例模式(懒汉式和双重检查模式)
两个区别
Balking 模式和 Guarded Suspension 模式从实现上看似乎没有多大的关系,Balking 模式只需要用互斥锁就能解决,而 Guarded Suspension 模式则要用到管程这种高级的并发原语;但是从应用的角度来看,它们解决的都是“线程安全的 if”语义,不同之处在于,Guarded Suspension 模式会等待 if 条件为真,而 Balking 模式不会等待。
Balking 模式的经典实现是使用互斥锁,你可以使用 Java 语言内置 synchronized,也可以使用 SDK 提供 Lock;如果你对互斥锁的性能不满意,可以尝试采用 volatile 方案,不过使用 volatile 方案需要你更加谨慎