Java中的双重检查(Double-Check)详解
https://zhuanlan.zhihu.com/p/31167297
双重检查锁和单例类
单例类在Java开发者中非常常用,但是它给初级开发者们造成了很多挑战。他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,无论任何原因,如何防止单例类有多个实例。在整个应用生命周期中,要保证只有一个单例类的实例被创建,双重检查锁(Double checked locking of Singleton)是一种实现方法。顾名思义,在双重检查锁中,代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建。
顺便提一下,在JDK1.5中,Java修复了其内存模型的问题。在JDK1.5之前,这种方法会有问题。本文中,我们将会看到怎样用Java实现双重检查锁的单例类,为什么Java 5之前的版本双重检查锁会有问题,以及怎么解决这个问题。
为什么你需要双重检查锁来实现单例类
一个常见情景,单例类在多线程环境中违反契约。如果你要一个新手写出单例模式,可能会得到下面的代码:
private static Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
然后,当你指出这段代码在超过一个线程并行被调用的时候会创建多个实例的问题时,他很可能会把整个getInstance()方法设为同步(synchronized),就像我们展示的第二段示例代码getInstanceTS()方法一样。尽管这样做到了线程安全,并且解决了多实例问题,但并不高效。在任何调用这个方法的时候,你都需要承受同步带来的性能开销,然而同步只在第一次调用的时候才被需要,也就是单例类实例创建的时候。这将促使我们使用双重检查锁模式(double checked locking pattern),一种只在临界区代码加锁的方法。程序员称其为双重检查锁,因为会有两次检查 _instance == null,一次不加锁,另一次在同步块上加锁。这就是使用Java双重检查锁的示例:
public static Singleton getInstanceDC() {
if (_instance == null) { // Single Checked
synchronized (Singleton.class) {
if (_instance == null) { // Double checked
_instance = new Singleton();
}
}
}
return _instance;
}
这个方法表面上看起来很完美,你只需要付出一次同步块的开销,但它依然有问题。除非你声明_instance变量时使用了volatile关键字。没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的_instance的情况,但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。对于volatile变量_instance,所有的写(write)都将先行发生于读(read),在Java 5之前不是这样,所以在这之前使用双重检查锁有问题。现在,有了先行发生的保障(happens-before guarantee),你可以安全地假设其会工作良好。另外,这不是创建线程安全的单例模式的最好方法,你可以使用枚举实现单例模式,这种方法在实例创建时提供了内置的线程安全。另一种方法是使用Initialization on Demand Holder(IODH)
枚举类实现
package singleton;/**
* @Author: GeXiaoSong
* @Date: 2021/9/14 22:16
*/
/**
* 使用枚举类实现单例模式
*
* @author GeXiaosong
* @date 2021/09/14 22:16
**/
public class EnumSingleton {
/**
* 私有化构造函数
*
* @return
* @author GeXiaosong
* @date 2021/09/14 22:17
**/
private EnumSingleton() {
}
/**
* 定义一个静态枚举类
*
* @param null
* @author GeXiaosong
* @date 2021/09/14 22:21
* @return
**/
static enum SingletonEnum {
// 创建一个枚举对象,天生单例
INSTANCE;
private EnumSingleton singleton;
// 私有化枚举类的构造函数
private SingletonEnum() {
singleton = new EnumSingleton();
}
public EnumSingleton getInstance() {
return singleton;
}
}
/**
* 对外暴露一个获取EnumSingleton对象的静态方法
* @author GeXiaosong
* @date 2021/09/14 22:23
* @return singleton.EnumSingleton
**/
public static EnumSingleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
}
class Test {
public static void main(String[] args) {
System.out.println(EnumSingleton.getInstance());
System.out.println(EnumSingleton.getInstance());
// 最后打印结果为true
System.out.println(EnumSingleton.getInstance() == EnumSingleton.getInstance());
}
}
Initialization on Demand Holder(IODH)
/**
* Initialization on Demand Holder(IODH)实现单例模式
*
* @author GeXiaosong
* @date 2021/09/14 22:28
**/
public class IODHSingleton {
static class IODHSingletonHolder {
static IODHSingleton instance = new IODHSingleton();
}
public static IODHSingleton getInstance() {
return IODHSingletonHolder.instance;
}
}