单例模式是一个比较"简单"的模式,其定义以下:java
保证一个类仅有一个实例,并提供一个访问它的全局访问点。git
或者数据库
Ensure a class has only one instance, and provide a global point of access to it.设计模式
确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。安全
请注意"简单"二字的双引号,说它简单它也简单,可是要想用好、用对其实并不那么简单,为何这么说?markdown
延迟加载多线程
线程安全jvm
破坏单例的状况ide
序列化函数
若是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; } } 复制代码
优势:延迟加载,线程安全,而且效率也很不错
缺点:实现相对复杂一点,JDK1.5之后才支持volatile
说明
这里针对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;普及度较前几种方式不高
在一个系统中,要求一个类有且仅有一个对象,若是出现多个对象就会出现“不良反应”,能够采用单例模式,具体的场景以下:
参考文献:《设计模式之禅》、《Effective Java》