本博客 猫叔的博客,转载请申明出处阅读本文约 “15分钟”java
适读人群:Java 中级git
学习笔记,休息了两天(其实期间在作一个模拟项目实战),偶尔也想到本身究竟应该作些什么,是真的对本身或社会有意义的呢?github
Photo on Visual hunt数据库
emmm,答案不止一个,今天先介绍一个简单易懂的设计模式
读题:咱们应该如何保证共享变量访问的线程安全,同时又避免引入锁产生的开销呢数组
在并发环境下,一个对象是很容易被多个线程共享的,那么对于数据的一致性是有要求的安全
虽然可使用显式锁或者CAS操做,不过这也会带来一些上下文切换等额外开销多线程
先举个例子说明下目前的问题吧架构
/** * @ClassName Cup * @Description 杯子 非线程安全 * @Author MySelf * @Date 2019/9/25 21:28 * @Version 1.0 **/ public class Cup { //直径 private double diameter; //高度 private double height; public double getDiameter() { return diameter; } public double getHeight() { return height; } //非原子操做 public void setCup(double diameter,double height){ this.diameter = diameter; this.height = height; } }
上面这段代码,你们应该都能看出是非线程安全的对吧(若是你看不出来,翻上一篇文章复习下)并发
由于在咱们对setCup操做赋值其直径的时候,可能另外一个线程已经开始读取他的高度了,那么这就会出现线程安全问题。
那么在不使用锁的状况,能够怎么作呢?
好好往下看呗,和刷朋友圈的时间差很少,一会儿就懂了
是的,今天说的方式就是讲Cup变成不可变对象!
不可变对象:对象一经建立,其对外可见的状态就保持不变(相似String、Integer)
那么上面的Cup须要怎么修改呢?
/** * @ClassName Cup * @Description 不可变对象,线程安全 * @Author MySelf * @Date 2019/9/25 21:32 * @Version 1.0 **/ public final class Cup { private final double diameter; private final double height; public Cup(double diameter,double height){ this.diameter = diameter; this.height = height; } }
这下不就好啦,永远不会改变了
等下,那我要怎么修改Cup呀?就算是并发操做,个人业务也可能会须要修改这个Cup呀
让咱们调整一下视野,修改Cup属性 == 替换Cup实例
假设咱们是一家茶杯铸模工厂,有5条流水线在生成最近的网红茶杯,不过由于互联网趋势的印象,偶尔须要小改动我们的这个茶杯参数,停机生产会亏本的,因此在模具适配器的代码上我们能够在使用不可变对象的状况下更换茶杯属性
/** * @ClassName MoldAdapter * @Description 模具适配器 * @Author MySelf * @Date 2019/9/25 21:35 * @Version 1.0 **/ public class MoldAdapter { private Map<String,Cup> cupMap = new ConcurrentHashMap<String, Cup>(); public void updateCup(String version,Cup newCup){ cupMap.put(version, newCup); } }
这里ConcurrentHashMap内部涉及的锁,和Demo中的茶杯新建、替换并没有关系,其过程不涉及锁
可能还有点模糊,说说娃娃机案例?
记得好久之前还在泡老婆的时候,带她去玩娃娃机,夸下海口说必定能抓到她要的那一只,结果·····
它叫 50
如今轮到咱们翻身作主人了,哼
假设咱们是一个片区娃娃机的头儿,每一个娃娃机都有他们对应的机器编号、支付二维码url、机械手频率(对,非职业机械工做者,这里给的是假设,这个才是赚钱的重点),假设咱们是一个嗜钱如命的短裤青年,每晚都清算了一次收益清单。
最近恰逢国庆期间,游客人数即将上涨····
插入,特此【Java猫说】公众号提早预祝祖国70周年繁荣昌盛、国泰民安!
想赚钱的想法,搜搜搜的一直往胸口跳
那么好的手段就是娃娃机上的机械手频率了
我须要针对性的去修改部分娃娃机的属性,不过还好我一开始是有一张编码与娃娃机的关系映射表的
我将不可变对象的想法引入到本身的赚钱生意中去
首先是娃娃机对象,先变为不可变对象
/** * @ClassName DollMachineInfo * @Description 娃娃机不可变对象 * @Author MySelf * @Date 2019/9/25 21:51 * @Version 1.0 **/ public final class DollMachineInfo { //编号 private final String number; //支付二维码url private final String url; //机械手频率 private final int frequency; public DollMachineInfo(String number,String url,int frequency){ this.number = number; this.url = url; this.frequency = frequency; } public DollMachineInfo(DollMachineInfo dollMachineInfoType){ this.number = dollMachineInfoType.number; this.url = dollMachineInfoType.url; this.frequency = dollMachineInfoType.frequency; } public String getNumber() { return number; } public String getUrl() { return url; } public int getFrequency() { return frequency; } }
此次我须要修改的是编码与娃娃机的关系映射表,因此这个表也须要是不可变的,他须要支持我获取关系映射表,并且须要替换最新的关系映射内容
/** * @ClassName MachineRouter * @Description 机器信息表 * @Author MySelf * @Date 2019/9/25 21:57 * @Version 1.0 **/ public final class MachineRouter { //保证其在并发环境的内存可见性 private static volatile MachineRouter instance = new MachineRouter(); //code与机器之间的映射关系 private final Map<String,DollMachineInfo> routeMap; // 二、存储不可变量routeMap public MachineRouter(){ //将数据库表中的数据加载到内存,存为Map this.routeMap = MachineRouter.setRouteFromeDB(); } // 三、从db将数据存入Map private static Map<String, DollMachineInfo> setRouteFromeDB(){ Map<String, DollMachineInfo> map = new HashMap<String, DollMachineInfo>(); //DB 代码 return map; } // 一、初始化实例 public static MachineRouter getInstance(){ return instance; } /** * 根据code获取对应的机器信息 * @param code 对应编码 * @return 机器信息 */ public DollMachineInfo getMacheine(String code){ return routeMap.get(code); } /** * 修改当前MachineRouter实例 * @param newInstance 新的实例 */ public static void setInstance(MachineRouter newInstance){ instance = newInstance; } private static Map<String, DollMachineInfo> deepCopy(Map<String,DollMachineInfo> d){ Map<String, DollMachineInfo> result = new HashMap<String, DollMachineInfo>(); for (String key : d.keySet()){ result.put(key, new DollMachineInfo(d.get(key))); } return result; } public Map<String, DollMachineInfo> getRouteMap() { //防护性复制 return Collections.unmodifiableMap(deepCopy(routeMap)); } }
接下来就是在并发业务中去添加更新代码了
/** * @ClassName Worker * @Description 通信对接类 * @Author MySelf * @Date 2019/9/25 22:13 * @Version 1.0 **/ public class Worker extends Thread { @Override public void run(){ boolean isRouterModification = false; String updateMachineInfo = null; while (true){ //其他业务代码 /** * 在通信的Socket信息中解析,并更新数据表信息,再重置MachineRouter实例 */ if (isRouterModification){ if ("DollMachineInfo".equals(updateMachineInfo)){ MachineRouter.setInstance(new MachineRouter()); } } //其他业务代码 } } }
案例说完,有个基本概念,那么说点专业术语
这是不可变对象模式,Immutable Object
严格上说,不可变对象须要知足什么条件:
Immutable Object 模式有两个重要的东西,大家应该差很少知道的
ImmutableObject:负责存储一组不可变状态
Manipulator:维护ImmutableObject的变动,当须要变动时,则参与生成新的ImmutableObject实例
典型交互场景
是的,他确实能够知足咱们的题目要求,不过任何一种设计模式都有其适合的场景
通常比较适合:
注意的几点:
集合遍历在多线程环境常常被引入锁,以防止遍历过程当中该集合内部结构被改变
java.util.concurrent.CopyOnWriteArrayList就使用了ImmutableObject模式
固然也是须要场景的,在遍历比修改操做更加频繁的场景
其内部维护一个array变量用于存储集合,在你添加一个元素时,它会生成一个新的数组,将集合元素复制到新数组,并在最后一个元素设置为添加的元素,且新数组复制给array,
即array引用的数组能够等效一个ImmutableObject,注意是等效
因此,在遍历CopyOnWriteArrayList时,直接根据array实例生成一个Iterator实例,无须加锁
由于最后的CopyOnWriteArrayList我没有认真的看源码,因此就不细致展开讲,主要是你们能够理解不可变对象模式,最好能够写一个Demo出来,但愿你们能够在生产环境中使用到这一理念,文笔拙劣,见谅。
我是MySelf,还在坚持学习技术与产品经理相关的知识,但愿本文能给你带来新的知识点。
学习交流群:728698035
现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不按期干货。