先上结论:未采用双重检查锁定机制来实现的单例模式在多线程环境下不是线程安全的;;只有采用双重检查锁定机制来实现的单例模式在多线程环境下才是线程安全的!!!
1、首先是未采用双重检查锁定机制来实现的单例模式,代码如下:
public class Singleton {
// 在类加载时就完成了初始化,所以类加载比较慢
private static Singleton instance = new Singleton();
// 私有化构造函数,防止外部类实例化
private Singleton() {}
// 提供一个公开的静态方法,返回该类的实例
public static Singleton getInstance() {
return instance;
}
}
代码分析如下:
未采用双重检查锁定机制来实现的单例模式在多线程环境下可能不是线程安全的,原因如下:
因此,未采用双重检查锁定机制的单例模式在多线程环境下是不安全的。
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;
}
}
从多线程的角度来看,使用双重检查锁定机制实现单例模式是线程安全的,原因如下:
getInstance()
方法被多个线程同时调用时,它们都会首先检查uniqueInstance
是否为null
,如果uniqueInstance
已经被其他线程初始化了(即不为null
),那么这些线程就不需要进入同步块,从而节省了同步带来的开销。uniqueInstance
为null
,线程会进入同步块。synchronized (Singleton.class)
确保同一时刻只有一个线程能够执行同步块内的代码,这避免了多个线程同时创建Singleton
实例的情况。uniqueInstance
是否为null
,因为有可能在第一个线程进入同步块之前的短暂时间里,第二个线程已经初始化了uniqueInstance
。通过第二次检查,我们可以确保不会在uniqueInstance
已经被初始化的情况下再次初始化它。uniqueInstance
,使用volatile关键字保证了当一个线程初始化完成后,其他线程能立即看到初始化后的值,同时,它确保了在构造函数执行完成之前,不会将uniqueInstance
设置为非null
,从而避免了部分初始化对象的暴露。因此,在双重检查锁定机制确保了即使在多线程环境下,Singleton
类也只会被实例化一次,从而实现了线程安全的单例模式。这种方法相对于直接使用synchronized
关键字进行同步的方法来说,具有更高的效率,因为它只在第一次创建实例时需要进行同步。