【揭秘】如何写出线程安全的单例模式?

发布时间:2023年12月27日

【揭秘】如何写出线程安全的单例模式? - 程序员古德

先上结论:未采用双重检查锁定机制来实现的单例模式在多线程环境下不是线程安全的;;只有采用双重检查锁定机制来实现的单例模式在多线程环境下才是线程安全的!!!

代码案例

【揭秘】如何写出线程安全的单例模式? - 程序员古德

1、首先是未采用双重检查锁定机制来实现的单例模式,代码如下:

public class Singleton {  
    // 在类加载时就完成了初始化,所以类加载比较慢  
    private static Singleton instance = new Singleton();  
  
    // 私有化构造函数,防止外部类实例化  
    private Singleton() {}  
  
    // 提供一个公开的静态方法,返回该类的实例  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

代码分析如下:

未采用双重检查锁定机制来实现的单例模式在多线程环境下可能不是线程安全的,原因如下:

  1. 无同步措施:如果单例模式的实现没有采取任何同步措施,多个线程可能会同时进入创建实例的逻辑,导致创建多个实例,这显然违反了单例模式的初衷。
  2. 竞态条件:在没有同步的情况下,多个线程可能同时检查到单例实例为null,并尝试创建新实例,由于线程调度的非确定性,最终可能会创建出多个实例,造成竞态条件。
  3. 可见性问题:在没有使用volatile关键字或其他同步措施的情况下,一个线程对单例实例的初始化可能对其他线程不可见,这意味着其他线程可能会观察到一个未完全初始化的实例,从而引发错误行为。
  4. 指令重排序:Java编译器和处理器可能会对指令进行重排序优化,在没有同步或volatile关键字的情况下,这可能导致单例实例在构造函数执行完毕之前就被其他线程观察到,从而引发错误。

因此,未采用双重检查锁定机制的单例模式在多线程环境下是不安全的。

2、接着是采用了双重检查锁定机制来实现的单例模式,代码如下:

public class Singleton {  
    // volatile确保多线程正确处理Singleton实例  
    private volatile static Singleton uniqueInstance;  
  
    private Singleton() {  
    }  
  
    public static Singleton getInstance() {  
        if (uniqueInstance == null) { //第一次检查,不在需要同步的块中  
            synchronized (Singleton.class) { //同步块  
                if (uniqueInstance == null) { //第二次检查,在同步块中  
                    uniqueInstance = new Singleton();  
                }  
            }  
        }  
        return uniqueInstance;  
    }  
}

从多线程的角度来看,使用双重检查锁定机制实现单例模式是线程安全的,原因如下:

  1. 第一次检查:首先,当getInstance()方法被多个线程同时调用时,它们都会首先检查uniqueInstance是否为null,如果uniqueInstance已经被其他线程初始化了(即不为null),那么这些线程就不需要进入同步块,从而节省了同步带来的开销。
  2. synchronized关键字:如果uniqueInstancenull,线程会进入同步块。synchronized (Singleton.class)确保同一时刻只有一个线程能够执行同步块内的代码,这避免了多个线程同时创建Singleton实例的情况。
  3. 第二次检查:在同步块内部,再次检查uniqueInstance是否为null,因为有可能在第一个线程进入同步块之前的短暂时间里,第二个线程已经初始化了uniqueInstance。通过第二次检查,我们可以确保不会在uniqueInstance已经被初始化的情况下再次初始化它。
  4. volatile关键字:在Java中,volatile关键字确保变量的可见性和禁止指令重排序,对于uniqueInstance,使用volatile关键字保证了当一个线程初始化完成后,其他线程能立即看到初始化后的值,同时,它确保了在构造函数执行完成之前,不会将uniqueInstance设置为非null,从而避免了部分初始化对象的暴露。

因此,在双重检查锁定机制确保了即使在多线程环境下,Singleton类也只会被实例化一次,从而实现了线程安全的单例模式。这种方法相对于直接使用synchronized关键字进行同步的方法来说,具有更高的效率,因为它只在第一次创建实例时需要进行同步。

关注我,每天学习互联网编程技术 - 程序员古德

文章来源:https://blog.csdn.net/qusikao/article/details/135249832
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。