单例模式的7种创建方式
1.饿汉式
public final class SingletonObject1 { private static final SingletonObject1 instance = new SingletonObject1(); private SingletonObject1() { } public static SingletonObject1 getInstance() { return instance; } }
饿汉式的创建方法关键在于 instance作为类变量直接得到了初始化,这种方法的优点在于多线程环境下能够百分百地保证同步,在多线程环境下不可能被实例化两次,但是instance若是被加载后很长一段时间后才使用,就意味着instance实例开辟的堆内存会驻留更长的时间,所以更优的创建方式应是伴随着懒加载的。
2.懒汉式
public final class SingletonObject2 { private static SingletonObject2 instance == null; private SingletonObject2() { } public static SingletonObject2 getInstance() { if (null == instance) instance = new SingletonObject2(); return instance; } }
所谓懒汉式就是在使用的时候才去创建,这样就可以避免类在初始化时提前创建,但是这种方式有一个很大的缺点,在多线程的环境下,若一开始因为线程上下文切换的原因,两个线程都通过了null==instance的if循环,这样就是new出两个实例,无法保证单例的唯一性,所以有下面第三种方法。
3.懒汉式+同步方法
public final class SingletonObject3 { private static SingletonObject3 instance ; private SingletonObject3() { } public synchronized static SingletonObject3 getInstance() { if (null == instance) instance = new SingletonObject3(); return instance; } }
这种方法通过添加同步控制既满足了懒加载,又满足了instance实例的唯一性,但是,添加了同步控制后getInstance方法的调用是串行化的,效率较低,因此引出第四种创建方式---Double Check方式
4.Double-Check
public final class SingletonObject4 {
Socket socket; //模仿一些资源的实例化
private static SingletonObject4 instance ;
private SingletonObject4() {
this.socket = new Socket();
}
public static SingletonObject4 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject4();
}
}
return SingletonObject4.instance;
}
}
若有两个线程通过了第一个Check循环,进入第二个Check循环是串行化的,只能有一个线程进入,这样当这个线程创建完成后,另外的线程就无法通过第二个循环了,保证了实例的唯一性,随后的线程也不会通过第一个Check循环,也就不会有同步控制的环节了。但是,这种方法也伴随着一个缺点,它可能会引起空指针的异常。
假设这个单例创建有一些其他的资源,例如Socket、Connection,这些资源在构造函数中也会被实例化,那样在创建单例的时候,就是要实例化自身还有Socket这些资源,那根据JVM的重排序和Happens-before原则,有可能会出现先实例化自身,再去实例化Socket这些资源,若在此时只实例化了自己的情况下,别的线程调用了这个单例中Socket这些资源的方法,而此时它们可能还没有被实例化,这样就会抛出空指针的异常,在此引出第五种创建方法----Volatile+Double-Check
5.Volatile+Double-Check
public final class SingletonObject5 { private volatile static SingletonObject5 instance ; private SingletonObject5() { } public static SingletonObject5 getInstance() { if (null == instance) { synchronized (SingletonObject5.class) { if (null == instance) instance = new SingletonObject5(); } } return SingletonObject5.instance; } }
volatile关键字可以防止重排序的发生,在此不对volatile作详细介绍,通过volatile关键字,这种模式可以说是满足懒加载、多线程下单例的唯一性、安全性的。
6.Holder方式
public final class SingletonObject6{ private SingletonObject6() { } private static class InstanceHolder { private static SingletonObject6 instance = new SingletonObject6(); } public static SingletonObject6 getInstance() { return InstanceHolder.instance; } }
Holder这种方式是本人最喜欢的一种创建方式,它借助了类加载的特点,在SingletonObject6中并没有instance的静态成员,而是放置了静态内部类InstanceHolder之中,因此SingletonObject6的初始化过程中并不会实例化instance,当Holder被主动引用的时候才会进行实例化,而在instance被实例化时是会在Java程序编译器中收集至<cliinit>()方法中的,该方法是同步方法,且保证内存的可见性、原子性和顺序性,可以说是饿汉方式的优化版,这种创建方式是最为广泛的方式之一。
7.枚举法
public enum EnumSingleton { Instance; public void method(){ } }
枚举法是《Effective Java》中作者推荐的方式 ,这种方法极为简单,因为枚举类型本身是final的,不允许被继承,且同样是线程安全的,且只能被实例化一次和不用考虑序列化之类的问题,使用的时候可以直接EnumSingleton.Instance.method()就可以使用了,但是它不能实现懒加载,比如调用其中的静态方法也是会实例化Instance的,读者可以自行测试,但是也可以进行改造,让枚举充当Holder的角色增加懒加载的特性,代码如下
public final class SingletonObject7 { private SingletonObject7() {} private enum Singleton { INSTANCE; private final SingletonObject7 instance; Singleton(){ instance = new SingletonObject7(); } public SingletonObject7 getInstance() { return instance; } } public static SingletonObject7 getInstance() { return Singleton.INSTANCE.getInstance(); } }