Java与设计模式之单例模式(下) 安全的单例模式
- new,无风险。由于单例模式提供的构造函数都是私有的,所以不能在外部使用new的方式创建对象。
- 克隆,无风险。因为对象必须实现一个Cloneable 接口才可以直接调用clone(),而单例模式并未实现Cloneable 接口,所以,无风险。
- 序列化机制,有风险。对象序列化成一个字节流后,若要被反序列化恢复时,会生成一个新的对象,此对象和原来的对象具有一模一样的行为,但归根结底是两个对象。
- 反射机制,有风险。有句老话“反射可以打破一切封装!”,说明了任何类在反射机制面前都是透明的,通过反射机制可以获得类的各种属性,当然也可以获得类的构造函数(包括私有的),从而构造一个新的对象。但是,枚举类除外,下文会提到。
通过反射破坏单例
private static void reflectMethod() {
try {
EagerSingleton instance1 = EagerSingleton.getInstance();
// 通过反射得到其构造方法,修改其构造方法的访问权限,并用这个构造方法构造一个对象
Constructor constructor = EagerSingleton.class.getDeclaredConstructor();
// 把私有访问域的访问级别设置为public,否则,会抛异常
constructor.setAccessible(true);
EagerSingleton instance2 = (EagerSingleton) constructor.newInstance();
System.out.println( "反射是否破坏了单例 : " + !(instance1 == instance2)); // true
} catch (Exception e) {
e.printStackTrace();
}
}
显然,说好的单例已经变成了多例。防止反射破坏单例可以使用枚举式单例模式。
private static void reflectEnumMethod() {
try {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
Constructor constructor = EnumSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
EnumSingleton instance2 = (EnumSingleton) constructor.newInstance();
System.out.println( "反射是否破坏了单例 : " + !(instance1 == instance2)); // true
} catch (Exception e) {
e.printStackTrace();
}
}
执行改方法时抛出如下异常:
java.lang.NoSuchMethodException: com.east7.singleton.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.east7.controller.SingletonController.reflectEnumMethod(SingletonController.java:42)
at com.east7.controller.SingletonController.main(SingletonController.java:22)
不言而喻,我们在代码中无法通过反射获取 enum 类的构造方法。对于枚举,JVM 会自动进行实例的创建,其构造方法由 JVM 在创建实例的时候进行调用。
通过序列化机制破坏
private static void serialMethod() {
try {
EagerSingleton instance1 = EagerSingleton.getInstance();
// instance3 将从 instance1 序列化后,反序列化而来
EagerSingleton instance3 = null;
ByteArrayOutputStream bout = null;
ObjectOutputStream out = null;
try {
bout = new ByteArrayOutputStream();
out = new ObjectOutputStream(bout);
out.writeObject(instance1);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
instance3 = (EagerSingleton) in.readObject();
} catch (Exception e) {
System.out.println(" -------------- " + e);
} finally {
// close bout&out
}
System.out.println( "序列化是否破坏了单例 : " + (instance1 == instance3));
} catch (Exception e) {
e.printStackTrace();
}
}
执行结果打印为false,说明生成了不同的实例。序列化饿汉式单例:
import java.io.ObjectStreamException;
import java.io.Serializable; /**
* 序列化的饿汉式单例,线程安全
*/
public class EagerSingletonPlus implements Serializable { private static final EagerSingletonPlus instance = new EagerSingletonPlus();
// 私有化构造方法 private EagerSingletonPlus() {
}
public static EagerSingletonPlus getInstance() {
return instance;
}
/**
* 看这里,新增
*/
public Object readResolve() throws ObjectStreamException {
return instance;
} // 序列化,新增
private static final long serialVersionUID = -3006063981632376005L;
}
把serialEnumMethod中的EagerSingleton类替换成EagerSingletonPlus,再次执行,结果变成了false,说明反序列化生成的实例instance3和instance1对应同一个实例。因为在反序列化的时候,JVM 会自动调用 readResolve() 这个方法,我们可以在这个方法中替换掉从流中反序列化回来的对象。关于readResolve()的更详细信息,请参考3.7 The readResolve Method 。下面验证基于枚举创建的单例类是不会被序列化破坏的,代码结构和serialMethod()类似。
private static void serialEnumMethod() {
try {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
// instance3 将从 instance1 序列化后,反序列化而来
EnumSingleton instance3 = null;
ByteArrayOutputStream bout = null;
ObjectOutputStream out = null;
try {
bout = new ByteArrayOutputStream();
out = new ObjectOutputStream(bout);
out.writeObject(instance1);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
instance3 = (EnumSingleton) in.readObject();
} catch (Exception e) {
System.out.println(" - EagerSingletonPlus ------------- " + e);
} finally {
// close bout&out
}
System.out.println( "枚举自带光环,反序列化未破坏单例 : " + (instance1 == instance3)); // true
} catch (Exception e) {
e.printStackTrace();
}
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum const " + enumType +"." + name);
}
从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。所以,JVM对序列化有保证。
Reference
Java与设计模式之单例模式(下) 安全的单例模式的更多相关文章
- 单例模式——Java EE设计模式解析与应用
单例模式 目录: 一.何为单例 二.使用Java EE实现单例模式 三.使用场景 一.何为单例 确保一个类只有一个实例,并且提供了实例的一个全局访问点 1.1 单例模式类图 ...
- Java设计模式学习笔记,一:单例模式
开始学习Java的设计模式,因为做了很多年C语言,所以语言基础的学习很快,但是面向过程向面向对象的编程思想的转变还是需要耗费很多的代码量的.所有希望通过设计模式的学习,能更深入的学习. 把学习过程中的 ...
- Java与设计模式之单例模式(上)六种实现方式
阎宏博士在<JAVA与模式>中是这样描述单例模式的:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. ...
- 设计模式-单例模式下对多例的思考(案例:Server服务器)
前述: 在学习单例模式后,对老师课上布置的课后作业,自然要使用单例模式,但是不是一般的单例,要求引起我的兴趣,案例是用服务器. 老师布置的要求是:服务器只有一个,但是使用这个服务器时候可以有多个对象( ...
- c++设计模式之单例模式下的实例自动销毁(垃圾自动回收器)
关于C++单例模式下m_pinstance指向空间销毁问题,m_pInstance的手动销毁经常是一个头痛的问题,内存和资源泄露也是屡见不鲜,能否有一个方法,让实例自动释放. 解决方法就是定义一个内部 ...
- Retrofit源码设计模式解析(下)
本文将接着<Retrofit源码设计模式解析(上)>,继续分享以下设计模式在Retrofit中的应用: 适配器模式 策略模式 观察者模式 单例模式 原型模式 享元模式 一.适配器模式 在上 ...
- Java的设计模式
一.什么是设计模式: 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. ...
- Java EE设计模式(主要简单介绍工厂模式,适配器模式和模板方法模式)
Java EE设计模式分为三种类型,共23种: 创建型模式:单例模式.抽象工厂模式.建造者模式.工厂模式.原型模式. 结构型模式:适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代理模式 ...
- Java面试题全集(下)转载
Java面试题全集(下) 这部分主要是开源Java EE框架方面的内容,包括hibernate.MyBatis.spring.Spring MVC等,由于Struts 2已经是明日黄花,在这里就不 ...
随机推荐
- IdentityServer4实现OAuth2.0四种模式之授权码模式
接上一篇:IdentityServer4实现OAuth2.0四种模式之隐藏模式 授权码模式隐藏码模式最大不同是授权码模式不直接返回token,而是先返回一个授权码,然后再根据这个授权码去请求token ...
- [jsp学习笔记] jsp基础知识 数据初始化、同步
- 纯JavaScript实现的调用设备摄像头并拍照的功能
这篇文章本来不在Jerry计划内的,咱们SAP中国研究院今天已经正式上班了,Jerry也回到工作岗位开始搬砖了. 今天一位同事问我关于本文标题描述的功能如何实现,Jerry在网上随便搜了一下,类似的例 ...
- UML类图的几种关系总结
本文摘自:UML类图关系总结 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregati ...
- Python——元组
是为了满足,某些值当被定义以后就不可修改或删除而出现的元组形式. 特点: 元组中的元素不可被修改或删除 没有独立的功能 可以进行嵌套,当嵌套方可以修改删除时,可以对嵌套方进行. 元组可以进行公共功能中 ...
- 邮件学习过程中关于request.POST的使用
今天的话主要的精力都集中在邮件系统demo的实现,因为自己个人技术有限,所以做出来的相当简陋,在其中碰到的问题其实也是比较多的,对于基于django的邮件相对来说比较容易,然后就是在提交数据,获取po ...
- Linux命令——systemctl
前言 systemctl本身的意义并不仅仅是一个命令那么简单,他标志着SysV时代的终结,Systemd时代的开始.CentOS 7.X系列已经抛弃SysV,全面拥抱Systemd这个init sys ...
- Qt5安装及组件选择(Qt 5.12.0)
组件选择 如下图所示,安装Qt时有选择组件这一步,全部安装未免太占磁盘控件,只需安装我们所需要的组件即可.接下来就分析分析各个组件的作用及含义. “Qt 5.12.0”节点下面是 Qt 的功能模块,包 ...
- UniChat-软件工程小组-第一次作业-选题
软件工程小组项目文档 小组成员:赵有为.张天善.宋春雨.郭凯璐.孙楠.冯韵瑶 Uni-Chat项目文档 需求分析Need 日常生活中我们在使用Ubuntu等系统时都会因为QQ等聊天工具对基于Lin ...
- 《你们都是魔鬼吗》实验十二 团队作业八:Alpha冲刺
<你们都是魔鬼吗>第八次团队作业:Alpha冲刺 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 你们都是魔鬼吗 作业学习目标 软件测 ...