单例模式是一个比较"简单"的模式,其定义如下:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
或者
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
请注意"简单"二字的双引号,说它简单它也简单,但是要想用好、用对其实并不那么简单,为什么这么说?
破坏单例的情况
序列化
如果Singleton类是可序列化的,仅仅在生声明中加上implements Serializable是不够的。为了维护并保证Singleton,必须声明所有实例域都是瞬时(transient)的,并且提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的对象。
反射
授权的客户端可以通过反射来调用私有构造方法,借助于AccessibleObject.setAccessible方法即可做到 。如果需要防范这种攻击,请修改构造函数,使其在被要求创建第二个实例时抛出异常。
private Singleton() { System.err.println("Singleton Constructor is invoked!"); if (singleton != null) { System.err.println("实例已存在,无法初始化!"); throw new UnsupportedOperationException("实例已存在,无法初始化!"); } } }
对象复制
在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过对象复制方式创建一个新对象,对象复制是不用调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。在一般情况下,类复制的情况不需要考虑,很少会出现一个单例类会主动要求被复制的情况,解决该问题的最好方法就是单例类不要实现Cloneable接口。
类加载器
如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
为解决懒汉式"线程安全问题",可以将getInstance()设置为同步方法,于是就有了第二种实现方式:
public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
如果不是特别需要延迟加载的场景,可以优先考虑饿汉式
public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
说明
这里针对volatile多说两句,很多书上和网上的双重检查锁实例都没有加volatile,事实上这是不正确的
首先,volatile的两层含义:
这里我们用到的主要是第二个语义。那么什么是指令重排序呢,就是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。简单理解,就是编译器对我们的代码进行了优化,在实际执行指令的的时候可能与我们编写的顺序不同,只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。
singleton = new Singleton();
这段代码在jvm执行时实际分为三步:
由于"指令重排"的优化,很可能执行步骤为1-3-2,即:对象并没有实例化完成但引用已经是非空了,也就是在第二处判空的地方为false,直接返回singleton——一个未完成实例化的对象引用。
这里涉及到Java内存模型、内存屏障等知识点,本文主要介绍单例模式,因此不再赘述,有兴趣的同学可以自行百度
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
与饿汉式的区别是,静态内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了延迟加载效果)。
因此静态内部类实现方式既能保证线程安全,也能保证单例的唯一性,同时也具有延迟加载特性
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }
优点:枚举方式具有以上所有实现方式的优点,同时还无偿地提供了序列化机制,防止多次实例化
缺点:JDK1.5以后才支持enum;普及度较前几种方式不高
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
源码地址: https://gitee.com/tianranll/j...
参考文献:《设计模式之禅》、《Effective Java》