Java并发 — Balking模式
2012 年 3 月 31 日
可以用“多线程版本的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() { } }
synchronized
// 编辑操作 public void edit() { synchronized (this) { changed = true; } } // 自动保存 private void autoSave() { synchronized (this) { // 没有修改,快速放弃 if (!changed) { return; } changed = false; } save(); }
- 共享变量changed是一个状态变量,业务逻辑依赖于这个状态变量的状态,本质上是if
-
在多线程领域,就是一种“多线程版本的if”,总结成一种设计模式,就是
Balking模式
Balking模式
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模式
-
上面用synchronized实现Balking模式的方式最为 稳妥
,建议在实际工作中采用 -
如果 对原子性没有要求
,可以使用volatile(仅能保证 可见性
)来实现Balking模式
// 能够用volatile实现Balking模式,是因为changed和rt的写操作不存在原子性要求 public class RouterTable { // = private Map<String, CopyOnWriteArraySet> 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 routers = rt.computeIfAbsent(router.getIFace(), iFace -> new CopyOnWriteArraySet()); routers.add(router); changed = true; } // 删除路由 public void remove(Router router) { Set 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; } }
Guarded Suspension + Balking
-
Balking模式只需要 互斥锁
就能实现,而Guarded Suspension模式则需要用到 管程
(高级并发原语) -
从应用角度来看,两者都是为了解决“线程安全的if”
-
Guarded Suspension模式会等待if条件为真(利用管程模型来实现),而Balking模式不会等待
-
转载请注明出处:http://zhongmingmao.me/2019/05/22/java-concurrent-balking/
访问原文「 Java并发 — Balking模式
」获取最佳阅读体验并参与讨论