设计模式——单例模式(防止序列化以及反射机制侵犯)
2017-04-06 14:37
639 查看
在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
创建单例很简单,总共三点:
私有构造器
声明一个私有的静态变量
提供一个对外的公共的静态方法访问该变量,如果该变量没有对象,则创建该对象
但是单例有很多种,至于哪一种才是天下无敌的,那要看你系统需要哪种,只有适合才是最好的。好了上代码。
第一种:懒汉式(线程不安全)
在单线程模式下可以使用这个模式创建,但是多线程场景下就万万不可了,该单例模式是线程不安全的,当系统创建的时候回出现多个instance。
第二种:懒汉式(线程安全)
第三种:饿汉式 (我好饿,我不管,我就要先拥有这个instance,线程安全但是效率比较低)
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy
loading的效果。
第四种:双重校验锁
第五种:静态内部类
第六种:枚举类单例
当然还有其他写法,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。
说到单例我会想到两个问题:
单例模式的序列化
单例模式的侵犯
下面是解决代码(我随便拿一个单例举例):
解决办法:
序列化单例,重写readResolve()方法
在私有构造器里判断intance,如存在则抛异常(防止反射侵犯私有构造器)
我把这两个放在同一个类里执行验证,所以下面需要各位亲手运行代码感受了。= =会有收获的。
这次总结让我更加加深了对单例模式,线程安全,反射侵犯以及序列化对单例的影响有了更加深刻的理解。总归一句话,没有强无敌的单例,适合系统才是最好的。
某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
创建单例很简单,总共三点:
私有构造器
声明一个私有的静态变量
提供一个对外的公共的静态方法访问该变量,如果该变量没有对象,则创建该对象
但是单例有很多种,至于哪一种才是天下无敌的,那要看你系统需要哪种,只有适合才是最好的。好了上代码。
第一种:懒汉式(线程不安全)
/** * 懒汉式 * 线程不安全 * @author Hacfqx * */ public class SingletonDemo1{ //声明一个私有的静态变量 private static SingletonDemo1 instance = null; //私有构造器 private SingletonDemo1(){} //提供一个对外的公共的静态方法访问该变量,如果该变量没有对象,则创建该对象 public static SingletonDemo1 getInstance(){ if (null == instance) { instance = new SingletonDemo1(); } return instance; } }
在单线程模式下可以使用这个模式创建,但是多线程场景下就万万不可了,该单例模式是线程不安全的,当系统创建的时候回出现多个instance。
第二种:懒汉式(线程安全)
/** * 懒汉式 * 线程安全 * @author Hacfqx * */ public class SingletonDemo2{ //声明一个私有的静态变量 private static SingletonDemo2 instance = null; //私有构造器 private SingletonDemo2(){} //此处增加同步锁synchronized public static synchronized SingletonDemo2 getInstance(){ if (null == instance) { instance = new SingletonDemo2(); } return instance; } }该写法在多线程环境能很好的工作,但是效率很低每次获取单例的时候都要先获取锁,这时候性能极其低下。不建议这样做
第三种:饿汉式 (我好饿,我不管,我就要先拥有这个instance,线程安全但是效率比较低)
public class SingletonDemo3 { private static SingletonDemo3 instance = new SingletonDemo3(); private SingletonDemo3() {} public static SingletonDemo3 getInstance(){ return instance; } }
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy
loading的效果。
第四种:双重校验锁
/** * synchronized锁 * * @author Hacfqx * */ public class SingletonDemo4 { private static SingletonDemo4 instance = null; private SingletonDemo4() { } /** * 双重校验 * 将synchronized关键字加在了内部, * 也就是说当调用的时候是不需要加锁的, * 只有在instance为null,并创建对象的时候才需要加锁, * 性能有一定的提升。 * 但是,这样的情况,还是有可能有问题的, * 看下面的情况:在Java指令中创建对象和赋值操作是分开进行的, * 也就是说instance = new Singleton();语句是分两步执行的。*但是JVM并不保证这两个操作的先后顺序,*也就是说有可能JVM会为新的Singleton实例分配空间,* 然后直接赋值给instance成员,然后再去初始化这个Singleton实例。 * 这样就可能出错了 * @return */ public static SingletonDemo4 getInstance() { if (null == instance) { synchronized (instance) { if (null == instance) { instance = new SingletonDemo4(); } } } return instance; } }
第五种:静态内部类
/** * 单例模式使用内部类来维护单例的实现, * JVM内部的机制能够保证当一个类被加载的时候, * 这个类的加载过程是线程互斥的。 * 这样当我们第一次调用getInstance的时候, * JVM能够帮我们保证instance只被创建一次, * 并且会保证把赋值给instance的内存初始化完毕, * 这样我们就不用担心上面的问题。 * 同时该方法也只会在第一次调用的时候使用互斥机制, * 这样就解决了低性能问题。 * * @author Hacfqx * */ public class SingletonDemo4 { private SingletonDemo4() { } /** * 静态内部类来维护单例 * @return */ private static class SingeletonHolder{ private final static SingletonDemo4 instance = new SingletonDemo4(); } /** * 获取实例 * @return */ public static SingletonDemo4 getInstance(){ return SingletonHolder.instance(); } }
第六种:枚举类单例
public enum EnumSingleton { INSTANCE; public void method(){ //TODO } }该写法不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象(具体可以查看enum内部实现)。在实际项目中比较少见。
当然还有其他写法,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。
说到单例我会想到两个问题:
单例模式的序列化
单例模式的侵犯
下面是解决代码(我随便拿一个单例举例):
解决办法:
序列化单例,重写readResolve()方法
在私有构造器里判断intance,如存在则抛异常(防止反射侵犯私有构造器)
我把这两个放在同一个类里执行验证,所以下面需要各位亲手运行代码感受了。= =会有收获的。
public class SingletonDemo6 implements Serializable{ // 类初始化时,不初始化这个对象(延迟加载,真正用的时候再创建) private static SingletonDemo6 instance; private SingletonDemo6() { // 防止反射获取多个对象的漏洞 if (null != instance) { throw new RuntimeException("单例模式被侵犯!"); } } public static synchronized SingletonDemo6 getInstance() { if (null == instance) instance = new SingletonDemo6(); return instance; } // 防止反序列化获取多个对象的漏洞。 // 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。 // 实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。 private Object readResolve() throws ObjectStreamException { return instance; } }
package com.zz.designpatterns.createpattern.singletonpattern; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; public class ReflectAndSerialSingleton { public static void main(String[] args) throws Exception { SingletonDemo6 sc1 = SingletonDemo6.getInstance(); SingletonDemo6 sc2 = SingletonDemo6.getInstance(); System.out.println(sc1); System.out.println(sc2); System.out.println(sc1.equals(sc2)); // sc1,sc2是同一个对象 /** * 通过反序列化的方式构造多个对象(类需要实现Serializable接口) */ // 1. 把对象sc1写入硬盘文件 FileOutputStream fos = new FileOutputStream("object.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(sc1); oos.close(); fos.close(); // 2. 把硬盘文件上的对象读出来 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")); // 如果对象定义了readResolve()方法,readObject()会调用readResolve()方法。从而解决反序列化的漏洞 SingletonDemo6 sc5 = (SingletonDemo6) ois.readObject(); // 反序列化出来的对象,和原对象,不是同一个对象。如果对象定义了readResolve()方法,可以解决此问题。 System.out.println(sc5); ois.close(); /** * 通过反射的方式直接调用私有构造器(通过在构造器里抛出异常可以解决此漏洞) */ Class<SingletonDemo6> clazz = SingletonDemo6.class; Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null); c.setAccessible(true); // 跳过权限检查 SingletonDemo6 sc3 = c.newInstance(); SingletonDemo6 sc4 = c.newInstance(); System.out.println(sc3); // sc3,sc4不是同一个对象 System.out.println(sc4); } }
这次总结让我更加加深了对单例模式,线程安全,反射侵犯以及序列化对单例的影响有了更加深刻的理解。总归一句话,没有强无敌的单例,适合系统才是最好的。
相关文章推荐
- 单例设计模式:饿汉式、懒汉式、枚举方式。以及涉及到的线程安全问题和反射机制问题。
- 增强可读性,易用性——设计模式之建造者模式(灵活使用继承和抽象类以及抽象函数和反射)
- C#用工厂模式、反射机制和缓存机制来设计数据访问层的接口总结
- JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制
- 反射机制:工厂设计模式
- (单例设计模式之一)饿汉式的反射与反序列化漏洞
- 简单工厂设计模式-反射机制
- java中的设计模式(二)--工厂方法模式 反射机制
- 类反射机制+责任链的设计模式demo
- JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制
- 利用反射机制实现工厂设计模式的高扩展性
- 设计模式之1.1 再谈反射 以及java当中通过配置文件得到类名字,并生成对象的方法
- 单例模式以及通过反射和序列化破解单例模式
- C#用工厂模式、反射机制和缓存机制来设计数据访问层的接口
- 利用反射机制实现工厂设计模式的高扩展性
- 反射机制与配置文件结合的工厂设计模式
- java中的设计模式(二)--工厂方法模式 反射机制
- 设计模式之用反射机制改进的抽象工厂
- 反射机制和工厂设计模式的使用
- Java反射机制详解:从classLoader到反射机制再到抽象工厂设计模式