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已经是明日黄花,在这里就不 ...
随机推荐
- js两个变量互换值
js两个变量交换值 这个问题看似很基础,但是有很多的实现方式,你知道的有多少呢,网上也有很多的方法,下面就来总结一下 中间变量(临时变量) 临时变量其实很好理解,通过一个中间变量进行交换值 var s ...
- Laravel5.6---从头做一个项目web
简介:类似论坛的项目,纯web,响应式,主要包含登录注册.支付.评论.富文本.文章列.个人中心等 目录: 1.初始化项目 ======================================= ...
- org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案
org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案 背景描述 Sprin ...
- centos 7.6 安装php70
1.首先查看是否有老版本 yum list installed | grep php 2.如果安装的有,清除老版本 yum remove php.x86_64 php-cli.x86_64 php-c ...
- Python如何打印文字对应的索引
用python编写一个简单的小程序:将文字对应的索引打印出来. test=input('>>>') print(test) l=len(test) print(l) r=range( ...
- Kotlin构造方法详解与初始化过程分析
在上一次https://www.cnblogs.com/webor2006/p/11192025.html已经初识了Kolin的面向对象,先来回顾一下: 介绍了primary 构造方法,其特点回忆一下 ...
- java.lang.IllegalArgumentException: No enum constant org.apache.ibatis.type.JdbcType.Integer
mybatis配置的jdbaType类型要是大写的,否则就会出现此种异常 原因是在xml中配置的 jdbcType中有小写字母
- 创建型模式(五) 原型模式(Prototype)
一.动机(Motivation) 在软件系统中,经常面临着"某些结构复杂的对象"的创建工作:由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口.如何应对 ...
- 二.protobuf3数据类型
定义数据类型 首先让我们看一个非常简单的例子.假设您想要定义搜索请求消息格式,其中每个搜索请求都有一个查询字符串.您感兴趣的特定结果页面以及每页的结果数量.这是用来定义消息类型的.proto文件. s ...
- 3. 控制反转(IoC)和依赖注入(DI)
1).控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的.在传统的编程方式中,业务逻辑的流程是由应用程序中的早已被设定好关联关系的 ...