单例设计模式总结-5种常见写法+防止发射反序列化
来源:互联网 发布:fifaol3数据库手机版 编辑:程序博客网 时间:2024/04/29 12:31
单例模式是设计模式中最常见的,也是最简单的一种,所谓单例,是需要在任何时候只存在一个对象实例,故显然需要私有化构造器,构造器私有了,要想获得这个实例,故必须在类内部创建对象实例,同时必须提供静态方法来获取,静态方法只能操作静态属性,故内部对象实例需要被static修饰,由于单例,可用final修饰;
单例存在多种写法,有各自不同的特点,下面介绍常用的写法,并且这些写法有些存在漏洞,如发射、发序列化可以破坏该单例;
一、单例模式的五种写法
1、饿汉式
public class Singleton {private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}}饿汉式在类加载后,直接创建了对象,从文章开头的解释出发,可以理解为什么用private、static这些关键字
2、懒汉式
class Singleton2{private static Singleton2 instance;private Singleton2(){}public static Singleton2 getInstance(){if(instance==null){ //A位置instance = new Singleton2();}return instance;}}懒汉式具有延迟加载的特点,即在需要用该对象的时候才会创建实例,在一定程度上可以节约点资源;
懒汉式vs饿汉式:饿汉式在类加载后直接创建对象(即使不需要使用对象),所以线程安全
懒汉式在需要使用时才会创建对象,非线程安全
工程中:建议使用饿汉式
懒汉式的问题:并发访问时,T1执行到A处暂停,T2同样执行到A处,并继续往下执行,
T2实例化了instance,T2执行完,T1线程继续执行,此时T1线程会继续执行instance = new Singleton2();
无法保证单例
3、单例双检锁模式
class Singleton3{private static volatile Singleton3 instance; //注意此处volatile关键字private Singleton3(){}public static /*synchronized*/ Singleton3 getInstance(){if(instance==null){ //A位置synchronized (Singleton3.class) {if(instance==null){instance=new Singleton3();// B位置}}}return instance;}}不加volatile关键字的双检锁模式,解决了懒汉式的线程安全问题,但它带来了新的问题
双检锁模式---存在问题(与Java内存模型有关)
理论上时很完美的,但是实际会因Java内存模型,设计指令重排序,出现问题
/**
*
* 一、好处:避免在函数上使用synchronized关键字,导致每次调getIstance()函数都要
* 读锁的开销,提高效率
* 二、潜在问题:
* instance=new Singleton3();分为3步
* 1)申请空间
* 2)初始化空间的值
* 3)将引用instance指向该空间
* 分析:实际应该让3步按照顺序来,但由于Java内存模型,允许他们不按顺序执行,试想:
* T1执行B处时,初始化时按照1)->3)->2)的顺序,刚好执行完3)就被中断了,
* 此时,T2执行到A处,判断instance==null发现instance不为null,于是
* 将该对象返回,而该对象并未被初始化,这就导致了问题
* 三、解决之道:单例类的成员用volatile关键字修饰,内部原理参考另一篇博客
4、静态内部类方式
class Singleton4{private Singleton4(){}private static class Singleton4Holder{private static Singleton4 instance = new Singleton4();}public static Singleton4 getInstance(){return Singleton4Holder.instance;}}
具有延迟加载特性,同时也是线程安全的,是比较推荐的写法
1.Singleton4类被加载的时候,并不会实例化instance对象
2.只有在调用getInstance()函数的时候,才开始加载Singleton4Holder类,并创建instance实例
5、枚举
enum Singleton5{INSTANCE;public void dosomething(){}}使用枚举方法的好处在于:
1.枚举天生就是线程安全的,其在任意情况下都是单例
2.枚举具有防止反射和发序列化的特点
二、单例模式防止反射和反序列化
1、防止发射,我们知道,可以通过发射方式来获取类的构造方法,并用纸创建对象,即便构造方法为private修饰的,为了防止发射的漏洞,只需在构造函数内部做个判断,如下:
private Singleton(){if(null!=instance){throw new RuntimeException("单例已经存在");}}2、防止反序列化
反序列化:即强对象写入磁盘再读入内存,得到一个新的实例,破坏了了单例的唯一性
Java提供了readResolve()方法,可以让开发者控制对象的反序列化
解决反序列化方法:在单例类中加入方法
本质:无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。
实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
private Object readResolve() throws ObjectStreamException {return instance;}3、以饿汉式为例,设计防止发射和反序列化漏洞的单例
class Singleton6 implements Serializable{private static Singleton6 instance = new Singleton6();//防止反射破坏单例private Singleton6(){if(null!=instance){throw new RuntimeException("单例已经存在");}}//防止反序列化破坏单例private Object readResolve() throws ObjectStreamException {return instance;}public static Singleton6 getInstance(){return instance;}}
4、以静态内部类方式为例,设计防止反射和反序列化的单例
class Singleton7 implements Serializable{//防止反射破坏单例模式private Singleton7(){if(null!=SingletonHolder.instance){throw new RuntimeException("单例已存在");}}//防止反序列化破坏单例模式 private Object readResolve() throws ObjectStreamException { return SingletonHolder.instance; } private static class SingletonHolder{private static Singleton7 instance = new Singleton7();}public static Singleton7 getInstance(){return SingletonHolder.instance;}}
总结:实际中,需要根据需要,选择合适的单例类型,从上面可以看出,一个单例涉及的知识点还是挺多的,如volatile关键字的原理和作用、线程安全问题、synchronized关键字的锁的对象是谁、反射和反序列化的原理,如何预防、类加载机制等等。
- 单例设计模式总结-5种常见写法+防止发射反序列化
- Java设计模式(一):单例模式,防止反射和反序列化漏洞
- Java设计模式(一):单例模式,防止反射和反序列化漏洞
- 单例模式,防止反射和反序列化漏洞
- 设计模式之使用序列化和反序列化实现单例模式
- (单例设计模式之一)饿汉式的反射与反序列化漏洞
- 设计模式--单例模式的5种写法
- 设计模式之单例模式的几种常见写法
- 单例模式及其序列化/反序列化
- 设计模式GOF23之单例模式效率测试与反射和反序列化问题的解决
- 单例防止暴力反射和反序列化创建对象
- 单例模式的两种常见写法
- Java 单例模式 五种常见的写法
- 单例模式的三种常见写法
- iOS之单例模式常见写法
- 设计模式之单例模式的两种写法
- 设计模式:单例模式7种写法
- 设计模式-单例模式七种写法(Java)
- symbol-versioning
- getchar()、gets()、getch()
- 7.7 设置缓存控制
- Angular4学习笔记(一)-环境搭建
- Python3.6.2 图形界面模块Tk (Day1)
- 单例设计模式总结-5种常见写法+防止发射反序列化
- ACM 大数 A+B Problem
- HDU 5938 Four Operations 模拟
- 记高德地图使用学习过程
- 如何在Angular中使用jquery
- 561. Array Partition I
- MyTask工具类
- 7.8 新鲜度详细算法
- Angular4学习笔记(二)-在WebStorm中启动项目