彻底搞懂单例模式如何安全的实现

Posted 余同学的开发之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底搞懂单例模式如何安全的实现相关的知识,希望对你有一定的参考价值。

单例设计模式,意味着整个系统中只能存在一个实例,比方说像日志对象这种。我们常说的有饿汉式和懒汉式这两种模式来创建单例对象,今天就拓展一下思维,多看几种。


首先我们若是想一个类只有一个对象,那肯定先要私有化构造器,断了在其它的类中使用构造器创建实例的念头。其它的类中不能创建,我们就只能在类中自己创建一个私有实例,另外还要提供一个共有的方法使其它对象获取到实例。所以,第一版出现了。


1 【饿汉式 V1】

在类加载的时候就创建实例


@ThreadSafe
public class SingletonExample2 {
    // 私有化构造器
    private SingletonExample2(){}
    // 提供一个实例
    private static SingletonExample2 instance = new SingletonExample2();
    // 提供共有的方法返回实例
    public static SingletonExample2 getInstance(){
        return instance;
    }
}


不要忘了在多线程环境中还有关注线程是否安全,我这里都会打上注解,@ThreadSafe 表示线程安全,@NotThreadSafe 表示线程不安全。


上面这种方式就是比较简单的,也是最容易想到的方式,就有一个缺点,若是不使用这个对象,那就有点浪费资源了,这个对象不一定会被使用,但是我们已经创建好了。


2 【饿汉式 V2】

这种方式是借助于 "静态代码块只会被加载一次" 来实现单例的创建,很简单,也很好理解,问题和饿汉式一样,不一定就会使用到这个对象,所以可能会出现浪费资源的情况。


@ThreadSafe
public class SingletonExample6 {
    // 私有化构造器
    private SingletonExample6(){}

    private static SingletonExample6 instance = null;

    static {
        instance = new SingletonExample6();
    }
    // 提供共有的方法返回实例
    public static SingletonExample6 getInstance(){
        return instance;
    }
}


3 【懒汉式 V1】

在对象使用的时候才创建实例


@NotThreadSafe
public class SingletonExample1 {
    // 私有化构造器
    private SingletonExample1(){}
    // 提供一个实例
    private static SingletonExample1 instance = null;
    // 提供共有的方法返回实例
    public static SingletonExample1 getInstance(){
        if(instance == null){
            return new SingletonExample1();
        }
        return instance;
    }
}


这种方式在单线程的时候是没有问题的,但是在多线程时就会出现问题,假如线程 A 进入 if 之后暂停执行,此时又来一个线程 B 还是可以进入 if 并返回一个实例,此时 A 再次获得执行时,返回的是另一个实例了。


4 【懒汉式 V2】

在共有方法上添加 synchronized 关键字,同步该方法。可行,但是不推荐使用,因为 synchronized 修饰方法之后,在同一时刻只能有一个线程执行该方法,一旦有线程获得方法,其它线程需要等待,这样会浪费大量时间,系统运行效率降低。


@ThreadSafe
@NotRecommend
public class SingletonExample3 {
    // 私有化构造器
    private SingletonExample3(){}
    // 提供一个实例
    private static SingletonExample3 instance = null;
    // 提供共有的方法返回实例
    public static synchronized SingletonExample3 getInstance(){
        if(instance == null){
            return new SingletonExample3();
        }
        return instance;
    }
}


5 【懒汉式 V3】

这种方式使用双重检测 + 防止指令重排的方式来保证线程安全,首先需要注意的是在 getInstance 方法中,我们需要双层检测并使用同步代码块将创建对象的过程同步起来。


 


因为在 new SingletonExample4() 的过程中,并不是一个原子操作,是可以进一步拆分为:


1、分配对象内存空间

memory = allocate()

2、初始化对象

initInstance()

3、设置 instance 指向刚分配的内存

instance = memory


在多线程的情况下,上面 3 个指令会存在指令重排序的情况。【JVM 和 CPU 指令优化】重排后的结果可能为:


memory = allocate()

instance = memory

initInstance()



为了防止指令重排带来的问题呢,我们就可以使用 volatile 关键字防止指令重排。这样就是线程安全的了。只需在上一版的基础上使用 volatile 修饰 instance 实例即可。


volatile 的语义就是添加内存屏障和防止指令重排,这在前面已经分析过了。



    private static volatile SingletonExample4 instance = null;


6 【使用枚举类实现单例模式】

这是推荐使用的方法,因为它比懒汉式的线程安全更容易保证,比饿汉式的性能高,它只有在调用的时候才实例对象。


@ThreadSafe
@Recommend
public class SingletonSpecial {
    private SingletonSpecial(){}

    public static SingletonSpecial getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        // public static final Singleton INSTANCE;

        private SingletonSpecial singleton;

        // JVM 来保证这个构造方法只会调用一次
        Singleton(){
            singleton = new SingletonSpecial();
        }

        public SingletonSpecial getInstance(){
            return singleton;
        }
    }
}


7 【使用静态内部类】

这种方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载 SingletonInstance 类,从而完成 Singleton 的实例化。


使用 static final 修饰之后 JVM 就会保证 instance 只会初始化一次且不会改变。


@ThreadSafe
@Recommend
public class SingletonExample7 {

    private SingletonExample7(){}

    private static class SingletonInstance{
        private static final SingletonExample7 instance = new SingletonExample7();
    }

    public static SingletonExample7 getInstance(){
        return SingletonInstance.instance;
    }
}


总结一下,今天主要说了单例模式的实现,并且在这中间,复习了一下前面说的线程安全的应用。若是对线程安全的原理以及实现有不懂的可以回头看看这几篇文章。




以上是关于彻底搞懂单例模式如何安全的实现的主要内容,如果未能解决你的问题,请参考以下文章

彻底搞懂单例模式如何安全的实现

一文彻底搞懂单例模式

设计模式系列彻底搞懂单例模式

一文彻底搞懂单例模式(Singleton-Pattern)

用一个通俗易懂的实战案例,彻底搞懂单例模式

被问到设计模式不要怂:一文读懂单例模式