Guarded Suspension 模式需要等待的,必须要等到条件为真。但有些场景下,我们需要快速放弃。最常见的例子是各种编辑器提供的自动保存功能。自动保存功能的实现逻辑一般都是隔一定时间自动执行存盘操作,存盘操作的前提是文件做过修改,如果文件没有执行过修改操作,就需要快速放弃存盘操作。
class AutoSaveEditor{
// 文件是否被修改过
boolean changed=false;
// 定时任务线程池
ScheduledExecutorService ses =
Executors.newSingleThreadScheduledExecutor();
// 定时执行自动保存
void startAutoSave(){
ses.scheduleWithFixedDelay(()->{
autoSave();
}, 5, 5, TimeUnit.SECONDS);
}
// 自动存盘操作
void autoSave(){
synchronized(this){
if (!changed) {
return;
}
changed = false;
}
// 执行存盘操作
// 省略且实现
this.execSave();
}
// 编辑操作
void edit(){
// 省略编辑逻辑
......
synchronized(this){
changed = true;
}
}
}
示例中的共享变量是一个状态变量,业务逻辑依赖于这个状态变量的状态:当状态满足某个条件时,执行某个业务逻辑,其本质其实不过就是一个 if 而已,放到多线程场景里,就是一种“多线程版本的 if”。这种“多线程版本的 if”的应用场景还是很多的,所以也有人把它总结成了一种设计模式,叫做Balking 模式。
Balking 模式的实现
- Balking 模式的经典实现是使用互斥锁,你可以使用 Java 语言内置 synchronized,也可以使用 SDK 提供 Lock
- 用 volatile 实现 Balking 模式,但使用 volatile 的前提是对原子性没有要求。
Balking 模式的应用
Balking 模式有一个非常典型的应用场景就是单次初始化,下面的示例代码是它的实现。这个实现方案中,我们将 init() 声明为一个同步方法,这样同一个时刻就只有一个线程能够执行 init() 方法;init() 方法在第一次执行完时会将 inited 设置为 true,这样后续执行 init() 方法的线程就不会再执行 doInit() 了。
class InitTest{
boolean inited = false;
synchronized void init(){
if(inited){
return;
}
// 省略 doInit 的实现
doInit();
inited=true;
}
}
线程安全的单例模式本质上其实也是单次初始化,所以可以用 Balking 模式来实现线程安全的单例模式。
class Singleton{
private static volatile
Singleton singleton;
// 构造方法私有化
private Singleton() {}
// 获取实例(单例)
public static Singleton
getInstance() {
// 第一次检查
if(singleton==null){
synchronize{Singleton.class){
// 获取锁后二次检查
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
网友评论