单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于建立型模式,它提供了一种建立对象的最佳方式。java
这种模式涉及到一个单一的类,该类负责建立本身的对象,同时确保只有单个对象被建立。这个类提供了一种访问其惟一的对象的方式,能够直接访问,不须要实例化该类的对象。设计模式
注意:安全
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。由于没有加锁 synchronized,因此严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工做。多线程
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种方式的好处是写起来简单,且绝对线程安全;坏处是并发性能极差,事实上彻底退化到了串行。单例只须要初始化一次,但就算初始化之后,synchronized的锁也没法避开,从而getInstance()彻底变成了串行操做。性能不敏感的场景建议使用。并发
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
这种方式比较经常使用,但容易产生垃圾对象,即类加载时初始化单例,之后访问时直接返回便可。性能
它基于类加载机制避免了多线程的同步问题,可是没有达到懒加载的效果优化
public class Singleton { private static Singleton instance; public int f1 = 1; // 触发部分初始化问题 public int f2 = 2; private Singleton(){} public static Singleton getInstance() { if (instance == null) { // 当instance不为null时,可能指向一个“被部分初始化的对象” synchronized (Singleton.class) { if ( instance == null ) { instance = new Singleton(); } } } return instance; } }
这种方式看起来彷佛已经达到了理想的效果:懒加载+线程安全。可是DCL仍然是线程不安全的,因为指令重排序,会遇到”部分初始化问题”。线程
问题出在这行简单的赋值语句:设计
instance = new Singleton();
它并非一个原子操做。事实上,它能够”抽象“为下面几条JVM指令:code
memory = allocate(); //1:分配对象的内存空间 initInstance(memory); //2:初始化对象(对f一、f2初始化) instance = memory; //3:设置instance指向刚分配的内存地址
上面操做2依赖于操做1,可是操做3并不依赖于操做2,因此JVM能够以“优化”为目的对它们进行重排序,通过重排序后以下:
memory = allocate(); //1:分配对象的内存空间 instance = memory; //3:设置instance指向刚分配的内存地址(此时对象还未初始化) ctorInstance(memory); //2:初始化对象
能够看到指令重排以后,操做 3 排在了操做 2 以前,即引用instance指向内存memory时,这段崭新的内存尚未初始化——即,引用instance指向了一个”被部分初始化的对象”。此时,若是另外一个线程调用getInstance方法,因为instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,用户获得了没有完成初始化的“半个”单例。
解决这个该问题,只须要将instance声明为volatile变量
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
这种方式能达到双检锁方式同样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的状况,双检锁方式可在实例域须要延迟初始化时使用。
这种方式一样利用了类加载机制来保证初始化 instance 时只有一个线程,它跟第饿汉式种方式不一样的是:饿汉式方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不必定被初始化。由于 SingletonHolder 类没有被主动使用,只有经过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
public enum Singleton { INSTANCE; public void whateverMethod() { } }
这种实现方式尚未被普遍采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止屡次实例化。