Java并发编程实战~Balking模式

上一篇文章中,我们提到可以用“多线程版本的 if”来理解 Guarded Suspension 模式,不同于单线程中的 if,这个“多线程版本的 if”是需要等待的,而且还很执着,必须要等到条件为真。但很显然这个世界,不是所有场景都需要这么执着,有时候我们还需要快速放弃。

需要快速放弃的一个最常见的例子是各种编辑器提供的自动保存功能。自动保存功能的实现逻辑一般都是隔一定时间自动执行存盘操作,存盘操作的前提是文件做过修改,如果文件没有执行过修改操作,就需要快速放弃存盘操作。下面的示例代码将自动保存功能代码化了,很显然 AutoSaveEditor 这个类不是线程安全的,因为对共享变量 changed 的读写没有使用同步,那如何保证 AutoSaveEditor 的线程安全性呢?

class AutoSaveEditor{//文件是否被修改过boolean changed=false;//定时任务线程池ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();//定时执行自动保存void startAutoSave(){ses.scheduleWithFixedDelay(()->{autoSave();}, 5, 5, TimeUnit.SECONDS);  }//自动存盘操作void autoSave(){if (!changed) {return;}changed = false;//执行存盘操作//省略且实现this.execSave();}//编辑操作void edit(){//省略编辑逻辑......changed = true;}
}

解决这个问题相信你一定手到擒来了:读写共享变量 changed 的方法 autoSave() 和 edit() 都加互斥锁就可以了。这样做虽然简单,但是性能很差,原因是锁的范围太大了。那我们可以将锁的范围缩小,只在读写共享变量 changed 的地方加锁,实现代码如下所示。

//自动存盘操作
void autoSave(){synchronized(this){if (!changed) {return;}changed = false;}//执行存盘操作//省略且实现this.execSave();
}
//编辑操作
void edit(){//省略编辑逻辑......synchronized(this){changed = true;}
}  

如果你深入地分析一下这个示例程序,你会发现,示例中的共享变量是一个状态变量,业务逻辑依赖于这个状态变量的状态:当状态满足某个条件时,执行某个业务逻辑,其本质其实不过就是一个 if 而已,放到多线程场景里,就是一种“多线程版本的 if”。这种“多线程版本的 if”的应用场景还是很多的,所以也有人把它总结成了一种设计模式,叫做 Balking 模式。

Balking 模式的经典实现

Balking 模式本质上是一种规范化地解决“多线程版本的 if”的方案,对于上面自动保存的例子,使用 Balking 模式规范化之后的写法如下所示,你会发现仅仅是将 edit() 方法中对共享变量 changed 的赋值操作抽取到了 change() 中,这样的好处是将并发处理逻辑和业务逻辑分开。

boolean changed=false;
//自动存盘操作
void autoSave(){synchronized(this){if (!changed) {return;}changed = false;}//执行存盘操作//省略且实现this.execSave();
}
//编辑操作
void edit(){//省略编辑逻辑......change();
}
//改变状态
void change(){synchronized(this){changed = true;}
}

用 volatile 实现 Balking 模式

前面我们用 synchronized 实现了 Balking 模式,这种实现方式最为稳妥,建议你实际工作中也使用这个方案。不过在某些特定场景下,也可以使用 volatile 来实现,但使用 volatile 的前提是对原子性没有要求。
在《Copy-on-Write 模式》中,有一个 RPC 框架路由表的案例,在 RPC 框架中,本地路由表是要和注册中心进行信息同步的,应用启动的时候,会将应用依赖服务的路由表从注册中心同步到本地路由表中,如果应用重启的时候注册中心宕机,那么会导致该应用依赖的服务均不可用,因为找不到依赖服务的路由表。为了防止这种极端情况出现,RPC 框架可以将本地路由表自动保存到本地文件中,如果重启的时候注册中心宕机,那么就从本地文件中恢复重启前的路由表。这其实也是一种降级的方案。
自动保存路由表和前面介绍的编辑器自动保存原理是一样的,也可以用 Balking 模式实现,不过我们这里采用 volatile 来实现,实现的代码如下所示。之所以可以采用 volatile 来实现,是因为对共享变量 changed 和 rt 的写操作不存在原子性的要求,而且采用 scheduleWithFixedDelay() 这种调度方式能保证同一时刻只有一个线程执行 autoSave() 方法。

//路由表信息
public class RouterTable {//Key:接口名//Value:路由集合ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> rt = new ConcurrentHashMap<>();    //路由表是否发生变化volatile boolean changed;//将路由表写入本地文件的线程池ScheduledExecutorService ses=Executors.newSingleThreadScheduledExecutor();//启动定时任务//将变更后的路由表写入本地文件public void startLocalSaver(){ses.scheduleWithFixedDelay(()->{autoSave();}, 1, 1, MINUTES);}//保存路由表到本地文件void autoSave() {if (!changed) {return;}changed = false;//将路由表写入本地文件//省略其方法实现this.save2Local();}//删除路由public void remove(Router router) {Set<Router> set=rt.get(router.iface);if (set != null) {set.remove(router);//路由表已发生变化changed = true;}}//增加路由public void add(Router router) {Set<Router> set = rt.computeIfAbsent(route.iface, r -> new CopyOnWriteArraySet<>());set.add(router);//路由表已发生变化changed = true;}
}

Balking 模式有一个非常典型的应用场景就是单次初始化,下面的示例代码是它的实现。这个实现方案中,我们将 init() 声明为一个同步方法,这样同一个时刻就只有一个线程能够执行 init() 方法;init() 方法在第一次执行完时会将 inited 设置为 true,这样后续执行 init() 方法的线程就不会再执行 doInit() 了。

class InitTest{boolean inited = false;synchronized void init(){if(inited){return;}//省略doInit的实现doInit();inited=true;}
}

线程安全的单例模式本质上其实也是单次初始化,所以可以用 Balking 模式来实现线程安全的单例模式,下面的示例代码是其实现。这个实现虽然功能上没有问题,但是性能却很差,因为互斥锁 synchronized 将 getInstance() 方法串行化了,那有没有办法可以优化一下它的性能呢?

class Singleton{private static Singleton singleton;//构造方法私有化  private Singleton() {}//获取实例(单例)public synchronized static Singleton getInstance(){if(singleton == null){singleton=new Singleton();}return singleton;}
}

办法当然是有的,那就是经典的双重检查(Double Check)方案,下面的示例代码是其详细实现。在双重检查方案中,一旦 Singleton 对象被成功创建之后,就不会执行 synchronized(Singleton.class){}相关的代码,也就是说,此时 getInstance() 方法的执行路径是无锁的,从而解决了性能问题。不过需要你注意的是,这个方案中使用了 volatile 来禁止编译优化,其原因你可以参考《01 | 可见性、原子性和有序性问题:并发编程 Bug 的源头》中相关的内容。至于获取锁后的二次检查,则是出于对安全性负责。

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;}
}

总结

Balking 模式和 Guarded Suspension 模式从实现上看似乎没有多大的关系,Balking 模式只需要用互斥锁就能解决,而 Guarded Suspension 模式则要用到管程这种高级的并发原语;但是从应用的角度来看,它们解决的都是“线程安全的 if”语义,不同之处在于,Guarded Suspension 模式会等待 if 条件为真,而 Balking 模式不会等待。
Balking 模式的经典实现是使用互斥锁,你可以使用 Java 语言内置 synchronized,也可以使用 SDK 提供 Lock;如果你对互斥锁的性能不满意,可以尝试采用 volatile 方案,不过使用 volatile 方案需要你更加谨慎。
当然你也可以尝试使用双重检查方案来优化性能,双重检查中的第一次检查,完全是出于对性能的考量:避免执行加锁操作,因为加锁操作很耗时。而加锁之后的二次检查,则是出于对安全性负责。双重检查方案在优化加锁性能方面经常用到,例如《17 | ReadWriteLock:如何快速实现一个完备的缓存?》中实现缓存按需加载功能时,也用到了双重检查方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496680.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jq如何在打开新的页面 关闭之前同链接的页面_教你如何“抢”其他域名的权重...

白帽波哥的上一篇文章《这三种域名&#xff0c;永远不会有询盘》里留了两个尾巴&#xff0c;一个是如何将好域名的权重导到一个新域名上&#xff0c;另一个是如何清除域名里的垃圾外链。今天先讲第一个&#xff0c;如何将好的老域名权重导到新域名&#xff0c;也就是业内常说的…

ARM GIC (五)gicv3架构-LPI

在gicv3中,引入了一种新的中断类型。message based interrupts,消息中断。 一、消息中断 外设,不在通过专用中断线,向gic发送中断,而是写gic的寄存器,来发送中断。 这样的一个好处是,可以减少中断线的个数。 为了支持消息中断,gicv3,增加了LPI,来支持消息中断。并且…

HtmlTextWriter学习

HtmlTextWriter与Html32TextWriter 这两个类不存在继承和被继承关系。HtmlTextWriter支持Html4.0标准&#xff0c;而Html32TextWriter支持Html3.2标准&#xff0c;在msdn中一般不建议将Html32TextWriter实例话&#xff0c;它仅为支持低版本的浏览器而设计。 HtmlTextWriter初始…

全球自动驾驶汽车发展指数哪家强?美德领衔,中国第七

来源&#xff1a;新智驾近日&#xff0c;罗兰贝格与德国著名汽车研究机构亚琛汽车工程技术有限公司共同发布《2017年第四季度全球自动驾驶汽车发展指数》报告&#xff0c;基于行业和市场两个维度&#xff0c;对全球主要汽车大国在自动驾驶领域的竞争态势做出全面比较。其中“行…

设计模式基本原则

最终目的&#xff1a;高内聚&#xff0c;低耦合 1) 开放封闭原则 (OCP,Open For Extension, Closed For Modification Principle) 类的改动是通过增加代码进行的&#xff0c;而不是修改源代码。 2) 单一职责原则 (SRP,Single Responsibility Principle) 类的职责要单一&a…

笔记:数据绑定表达式(一)

数据绑定表达式必须包含在<%#和%>字符之间。格式如下: <tagprefix:tagname property<%# data-binding expression %> runat"server" />或者如下&#xff1a; <%# data-binding expression %> ASP.NET 支持分层数据绑定模型&#xff0c;数据绑…

Java并发编程实战~Thread-Per-Message模式

我们曾经把并发编程领域的问题总结为三个核心问题&#xff1a;分工、同步和互斥。其中&#xff0c;同步和互斥相关问题更多地源自微观&#xff0c;而分工问题则是源自宏观。我们解决问题&#xff0c;往往都是从宏观入手&#xff0c;在编程领域&#xff0c;软件的设计过程也是先…

水面反光如何拍摄_如何在雨中拍摄,这些技巧会让你的摄影更完美

雨是柔弱的&#xff0c;是世界上最轻灵的东西&#xff0c;敲不响那钢筋水泥造的高楼。而瓦屋则不同&#xff0c;雨滴在上面&#xff0c;叮叮当当的&#xff0c;立即奏出悦耳的声音。身在小屋的人也就有了在雨中亲近自然的福气。而且在雨天睡觉是最舒服的了~在阴雨天气中最熟悉的…

2018全球最强物联网公司榜单揭晓|20家企业物联网战略大起底!

来源&#xff1a; 物联网智库丨公众号IDG旗下杂志《NetWork World》近期公布了全球最强物联网公司名单。本文根据入选评语&#xff0c;对20家企业战略布局进行搜集整理&#xff0c;供业内人士参考&#xff01;根据Gartner预测&#xff0c;到2020年将有超过200亿台联网设备。全球…

MD5算法

文章目录前言一、MD5是什么&#xff1f;二、go语言实现前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、MD5是什么&#xff1f; MD5信息摘要算法&#xff08;英语&#xff1a;MD5 Message-Digest Algorithm&#xff09;&#xff0c;一种被广泛…

Python 中 xpath 语法 与 lxml 库解析 HTML/XML 和 CSS Selector

The lxml.etree Tutorial &#xff1a;https://lxml.de/tutorial.html python3 解析 xml&#xff1a;https://www.cnblogs.com/deadwood-2016/p/8116863.html 微软文档&#xff1a; XPath 语法 和 XPath 函数 W3school Xpath 教程&#xff1a;http://www.w3school.com.cn/xp…

Java并发编程实战~生产者-消费者模式

前面我们在《Worker Thread 模式》中讲到&#xff0c;Worker Thread 模式类比的是工厂里车间工人的工作模式。但其实在现实世界&#xff0c;工厂里还有一种流水线的工作模式&#xff0c;类比到编程领域&#xff0c;就是生产者 - 消费者模式。 生产者 - 消费者模式在编程领域的…

4-css图片效果

设置边框border-style:dotted; /* 点画线 */ border-color:#FF9900; /* 边框颜色 */ border-width:5px; /* 边框粗细 */也可以合并书写 border:5px double #FF00FF;width 50% 表示目前此元素宽度相对于父元素宽度为50%float 用于设定图片与文字的混排方式margin用于设置距离…

AI界的七大未解之谜:OpenAI丢出一组AI研究课题

来源&#xff1a;三体智讯今天&#xff0c;OpenAI在官方博客上丢出了7个研究过程中发现的未解决问题。OpenAI希望这些问题能够成为新手入坑AI的一种有趣而有意义的方式&#xff0c;也帮助从业者提升技能。OpenAI版AI界七大未解之谜&#xff0c;现在正式揭晓——丨1. Slitherin难…

vue 前端商城框架_前端工程师要掌握几个Vue框架

vue是一套用于构建用户界面的渐进式JavaScript框架&#xff0c;简单说Vue是类似于view的前端框架。vue开发核心是关注视图层&#xff0c;同时它更加容易与第三方库结合&#xff0c;再者我们在现有的项目中可以直接整合一起。目前vue技术社区在英文或中文都非常丰富&#xff0c;…

sha256算法

文章目录前言一、sha256是什么&#xff1f;二、go语言实现前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、sha256是什么&#xff1f; SHA256是SHA-2下细分出的一种算法 SHA-2&#xff0c;名称来自于安全散列算法2&#xff08;英语&#xff…

Python 模块 requests 模拟登录豆瓣 并 发表动态

如何抓取 WEB 页面&#xff1a;http://blog.csdn.net/chenguolinblog/article/details/45024643github 上一个关于模拟登录的项目&#xff1a;https://github.com/xchaoinfo/fuck-login Python爬虫之模拟登录总结&#xff1a;http://blog.csdn.net/churximi/article/details/50…

华为云BU总裁:如何把AI从噱头变为生产力?

来源&#xff1a;亿欧网 作者&#xff1a;张之颖“别跟着喊口号&#xff0c;少看朋友圈。…人工智能在中国被过分炒作了&#xff0c;现在国内人工智能已被娱乐化。不是做两个刷脸应用、搞一个APP就叫做人工智能。”华为云BU总裁郑叶来接受环球网记者的采访时表示&#xff0c;华…

DSA签名算法

文章目录前言一、DSA是什么&#xff1f;二、go语言实现前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、DSA是什么&#xff1f; DSA&#xff08;Digital Signature Algorithm&#xff0c;数字签名算法&#xff0c;用作数字签名标准的一部分&a…

Java并发编程实战~Actor 模型

Hello Actor 模型 Actor 模型本质上是一种计算模型&#xff0c;基本的计算单元称为 Actor&#xff0c;换言之&#xff0c;在 Actor 模型中&#xff0c;所有的计算都是在 Actor 中执行的。在面向对象编程里面&#xff0c;一切都是对象&#xff1b;在 Actor 模型里&#xff0c;一…