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已经是明日黄花,在这里就不 ...
随机推荐
- 12.1 Mapping手动创建
只能在index里的field不存在的时候,才能指定新field的数据类型,field有数据后,就不能再修改field的类型了 可创建的类型如下: integer double date text/s ...
- 压测工具wrk的编译安装与基础使用
Linux上编译安装: [root@centos ~]# cd /usr/local/src [root@centos ~]# yum install git -y [root@centos ~]# ...
- Part_four:redis主从复制
redis主从复制 1.redis主从同步 Redis集群中的数据库复制是通过主从同步来实现的 主节点(Master)把数据分发从节点(slave) 主从同步的好处在于高可用,Redis节点有冗余设计 ...
- mysql 数据库 规范
目录 mysql 数据库 规范 基础规范 命名规范 表设计规范 字段设计规范 索引设计规范 SQL编写规范 行为规范 mysql 数据库 规范 基础规范 必须使用InnoDB存储引擎 解读:支持事务. ...
- Arm存储器
Arm可以引出27根地址线,只能实现128MB的寻址,那么要如何实现1GB的寻址呢?答案就是使用nGCS片选线,nGCSx为低电平为选中相应的外接设备.一共八根片选线,也就是bank1,bank2-以 ...
- redis被攻击,怎么预防
今天,自己的redis服务器被黑客攻击了,数据全部被删除 从图中可以看到,在db0中多了一个crackit,他就是罪魁祸首,他的值就是ssh无密码连接时需要的authorized_keys. 我们被攻 ...
- Python 依赖版本控制 (requirements.txt 文件生成和使用)
requirements.txt 最好配合虚拟空间使用, 虚拟空间的使用请参考 Python 虚拟空间的使用 - 难以想象的晴朗. requirements.txt 可以保证项目依赖包版本的确定性, ...
- Python——字符串增加颜色
给显示字符添加颜色: salary=int(input('\033[31;1m请输入你的工资:\033[0m')) ('\033[;1m请输入你的工资:\033[0m') 3x是给字符串改变颜色 31 ...
- CentOS上使用ntfs-3g挂载NTFS分区
U盘做过系统盘,是NTFS格式的,Centos7竟然不识别,而且因为一些原因,我的服务器没有联网,只能用U盘 查过资料才知道Centos7上默认是不支持挂载NTFS格式的分区的,需要安装ntfs-3g ...
- Access、Trunk和Hybrid三种端口模式
网络交换机(英语:Network switch)是一个扩大网络的器材,能为子网中提供更多的连接端口,以便连接更多的电脑. 通俗来说其起到的作用就是把一个网络端口分成多个网络端口 交换机和路由器的区别 ...