# 《并发设计模式》第05章-不可变模式-实现不可变类解决线程安全问题
作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)
沉淀,成长,突破,帮助他人,成就自我。
- 本章难度:★★☆☆☆
- 本章重点:掌握不可变模式的核心原理,熟练掌握不可变类的实现方式与原则,能够将不可变类熟练应用到自身实际项目中解决线程安全问题。
大家好,我是冰河~~
“本以为自己写的代码没问题,被老大一看竟然有这么多问题,不过还好算是明白哪里出问题了,也知道了出问题的原因,哎,自己重新实现吧,就有点找不到思路了。不可变,不可变,怎么设计这个类才是不会变化的呢?我感觉照老大之前的分析来看,很多实体类都会提供无参和有参构造方法,也会提供修改成员变量的getter/setter方法,这不就都是可变类了吗?”,小菜一直在心里嘀咕着。
# 一、情景再现
小菜听老王讲解的解决并发问题的方案后,对不可变类很感兴趣,于是下班后,自己尝试以“不可变类”模拟写了一个检票的功能小demo。满怀信心的想让老王给他看看,结果万万没想到。
# 二、寻求帮助
听完老王分析小菜的代码为何不是不可变类后,小菜下班回去认真思考了很久,可还是不知道怎么写一个不可变类。“还是到公司上班的时候问问老大吧。”,小菜最终还是要寻求老王的帮助了。
到了第二天,小菜还是起的特别早,早早来到公司,没想到老王已经坐在工位上忙事情了。“老大,今天来的真早啊”,小菜说道。
老王抬起头一看是小菜来了,便随口说道:“早,小菜,昨天跟你分析的代码听懂了吧?怎么样,自己写了不可变类解决线程安全问题吗?”。
“没有,我昨天下班到家想了很久,都不知道怎么写。很多实体类不是都会提供无参和有参构造方法吗?也会提供修改成员变量的getter/setter方法,这样的话,很多类都是可变的了,我确实没想出来改怎么写一个不可变类”。小菜回应道。
要不怎么说老王是个大好人呢?老王听后便说道:“我们拿着电脑去会议室吧,我给你讲讲怎么写不可变类”。
“好呀”,小菜听后,很高兴的回应到。
就这样,老王和小菜两个人都拿着电脑走进去了会议室。。。
PS:老王真特么是个大好人。。。
# 三、不可变类的原则
二人走到会议室后,老王放下电脑后说到:“我先给你讲讲写不可变类必须要满足的条件,也就是不可变类必须要遵循的原则”。
“好的”,小菜回应道。
“说起不可变类,必须要遵循。。。”,老王巴拉巴拉的说起来。
总体上讲,老王总结了如下几点:
(1)必须使用final关键字修饰不可变类,禁止被其他类继承,防止子类改变其方法和行为。
(2)必须使用final关键字修饰类中的所有成员变量,避免成员变量被修改。使用final修饰成员变量,也能保证成员变量在多线程并发环境下被其他线程访问时,一定是初始化完成的。
(3)必须使用private关键字修饰所有成员变量,防止子类和其他类通过成员变量的引用改变变量值。
(4)类中禁止提供修改内部状态的公开方法。
(5)创建对象的过程中,this关键字不能泄露给其他类,防止其他类(比如这个类的匿名内部类)在对象创建过程中修改状态。
(6)类中的任何字段,如果引用了状态可变的对象,比如集合、数组等,则这些字段也必须使用private修饰,这些字段以及字段的引用不能对外暴露,返回这些字段值时,必须进行防御性复制。
“这些原则能听懂吗?”,老王问小菜。
“有些能,有些就不能了”,小菜回答道。
“没事,我带着你把你上次的代码修改下,你就明白了”。老王说着便打开了自己的开发环境。
“好的”,小菜回应道。
# 四、实现不可变类
“不可变类之前我们也说过,它就是一经创建就不会再被改变的类”,老王边说边开始敲代码。
“首先,按照不可变类的原则来讲,我们先给User类和它的成员变量name与idCard都使用final关键字修饰,此时在User类中就会报错,要去掉set()方法,添加一个构造方法”,说话间,老王就将修改后的User类敲出来了(老王真牛)。
User类的源码详见:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.demo.right.User。
public final class User {
private final String name;
private final Long idCard;
public User(String name, Long idCard) {
this.name = name;
this.idCard = idCard;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", idCard=" + idCard +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
“这样User类就不能被其他类继承,也就是说,User类不会有任何子类来改变它的方法和行为,并且User里的成员变量都是使用final关键字修饰,也不会有其他方法来修改成员变量的值,User类能结合不可变类的原则看懂吗?”,老王继续说道。
“嗯,这里可以看懂”,小菜回应道。
“接下来,我们再来看TicketCheck类,之前你写的TicketCheck类的updateUser()方法中会分别传入userName和idCard来修改通过检票的人”,说着,老王打开了之前小菜写的代码,如下所示。
# 查看全文
加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码