单例模式的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();
    }
}

猜你喜欢

转载自www.cnblogs.com/luonote/p/10347427.html