可以用“多线程版本的if”来理解Guarded Suspension模式,必须等到条件为真,但很多场景需要 快速放弃
public class AutoSaveEditor {
// 文件是否被修改
// 非线程安全,对共享变量change的读写没有使用同步
private boolean changed = false;
// 定时任务线程池
private ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void startAutoSave() {
service.scheduleWithFixedDelay(() -> autoSave(), 5, 5, TimeUnit.SECONDS);
}
// 编辑操作
public void edit() {
changed = true;
}
// 自动保存
private void autoSave() {
// 没有修改,快速放弃
if (!changed) {
return;
}
changed = false;
save();
}
private void save() {
}
}
// 编辑操作
public void edit() {
synchronized (this) {
changed = true;
}
}
// 自动保存
private void autoSave() {
synchronized (this) {
// 没有修改,快速放弃
if (!changed) {
return;
}
changed = false;
}
save();
}
Balking模式本质上是一种 规范化 地解决“多线程版本的if”的方案
// 编辑操作
public void edit() {
// 仅仅将对共享变量changed的赋值操作抽取到change()
// 将并发处理逻辑和业务逻辑分开
change();
}
// 改变状态
private void change() {
synchronized (this) {
changed = true;
}
}
// 自动保存
private void autoSave() {
synchronized (this) {
// 没有修改,快速放弃
if (!changed) {
return;
}
changed = false;
}
save();
}
// 能够用volatile实现Balking模式,是因为changed和rt的写操作不存在原子性要求
public class RouterTable {
// <Key, Value> = <接口名,路由集合>
private Map<String, CopyOnWriteArraySet<Router>> rt = new ConcurrentHashMap<>();
// 路由表是否发生变化
private volatile boolean changed;
// 将路由表写入本地文件的线程池
private ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void startLocalSaver() {
service.scheduleWithFixedDelay(this::autoSave, 1, 1, TimeUnit.MINUTES);
}
// 保存路由表到本地文件
private void autoSave() {
// 没有修改,快速放弃
if (!changed) {
return;
}
changed = false;
save2Local();
}
private void save2Local() {
}
// 增加路由
public void add(Router router) {
CopyOnWriteArraySet<Router> routers = rt.computeIfAbsent(router.getIFace(), iFace -> new CopyOnWriteArraySet<>());
routers.add(router);
changed = true;
}
// 删除路由
public void remove(Router router) {
Set<Router> routers = rt.get(router.getIFace());
if (routers != null) {
routers.remove(router);
// 路由表发生变化
changed = true;
}
}
}
Balking模式有一个非常典型的应用场景就是 单次初始化
public class SingleInit {
private boolean inited = false;
public synchronized void init() {
if (inited) {
return;
}
doInit();
inited = true;
}
private void doInit() {
}
}
线程安全的单例模式本质上也是单次初始化,可以用Balking模式实现线程安全的单例模式
public class Singleton {
private static Singleton singleton;
// 私有构造函数
private Singleton() {
}
// 获取实例(单例),性能很差
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public class Singleton {
// volatile保证可见性
private static volatile Singleton singleton;
// 私有构造函数
private Singleton() {
}
// 获取实例(单例)
public static Singleton getInstance() {
// 第一次检查
if (singleton == null) {
synchronized (Singleton.class) {
// 第二次检查
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
转载请注明出处:http://zhongmingmao.me/2019/05/22/java-concurrent-balking/
访问原文「 Java并发 -- Balking模式 」获取最佳阅读体验并参与讨论