单例模式(Singleton),是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
单例模式一般符合以下三个条件:
-
私有的构造方法;
-
指向自己实例的私有静态引用;
-
以自己实例为返回值的静态的公有方法。
单例一般具有以下几种实现方法
1、饿汉式(立即加载)
// 饿汉式单例 public class Singleton { // 指向自己实例的私有静态引用,主动创建 private static Singleton singleton = new Singleton(); // 私有的构造方法 private Singleton(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton getSingleton(){ return singleton; } }
这种方法是类被加载时候,就实例化一个对象并交给自己的引用,即使在多线程下也是线程安全的,因为类只会被加载一次,缺点就是资源效率不高,可能getInstance()永远不会执行到。
2、懒汉式(延迟加载)
// 懒汉式单例 public class Singleton { // 指向自己实例的私有静态引用 private static Singleton singleton; // 私有的构造方法 private Singleton(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton getSingleton(){ // 被动创建,在真正需要使用时才去创建 if (singleton == null) { singleton = new Singleton(); } return singleton; } }
这种方法资源利用率高,不执行getInstance()就不会被实例,但是在多线程下,该方法存在线程安全问题。
3、线程安全的懒汉式
// 线程安全的懒汉式单例 public class Singleton { private static Singleton singleton; private Singleton(){} // 使用 synchronized 修饰,临界资源的同步互斥访问 public static synchronized Singleton getSingleton(){ if (singleton == null) { singleton = new Singleton(); } return singleton; } }
该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低。
4、改进的线程安全的懒汉式
// 线程安全的懒汉式单例 public class Singleton { //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例 private static volatile Singleton singleton; private Singleton() { } public static Singleton getSingleton() { // Double-Check idiom if (singleton == null) { synchronized (Singleton.class) { // 1 // 只需在第一次创建实例时才同步 if (singleton == null) { // 2 singleton = new Singleton(); // 3 } } } return singleton3; } }
使用双重检查保证了只有第一次创建的时候才会进入同步块,之后获取单例的时候不需要再去获取同步锁。但是这里要特别注意必须使用volatile关键字修饰单例引用。
因为
做第一次检查的时候,如果singleton!= null,并不代表singleton一定已经初始化完成了,造成这种情形的原因是指令重排序;
singleton = new Singleton(); 这句在底层经历了3个动作:
1.分配内存给这个对象;
2.初始化对象;
3.设置 singleton 指向刚分配的内存;
这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,singleton 指向刚分配的内存,导致Thread-0看到的 singleton 不为空,直接返回 singleton,但此时singleton 在Thread-1中还没有初始化,所以造成程序出问题;
用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;
5、静态内部类(延迟加载)
// 线程安全的懒汉式单例 public class Singleton5 { // 私有内部类,按需加载,用时加载,也就是延迟加载 private static class Holder { private static Singleton5 singleton5 = new Singleton5(); } private Singleton5() { } public static Singleton5 getSingleton5() { return Holder.singleton5; } }
该方法利用了JVM加载一个类时,其内部类不会同时被加载。达到了延迟加载的目的,又不会由线程安全问题
6、使用枚举
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }
上面的五种方法都可以利用反射实例化出另一个实例,存在安全问题,使用枚举则不会。