吃透面试一:DCL单例为什么要加volatile

一、什么是DCL

DCL的全拼是double check lock,也就是双重锁判断机制。

二、回顾几种单例

1、饿汉式

public class SingleInstance {
    private SingleInstance(){}
    private static final SingleInstance INSTANCE=new SingleInstance();
    public static SingleInstance getInstance(){
        return INSTANCE;
    }
}

优点:线程安全、调用效率高
缺点:不能延时加载

2、懒汉式

public class SingleInstance {
    private SingleInstance(){}
    private static SingleInstance INSTANCE;
    public static synchronized SingleInstance getInstance(){
        if (INSTANCE==null){
            INSTANCE=new SingleInstance();
        }
        return INSTANCE;
    }
}

优点:线程安全、能延时加载。
缺点:锁的粒度太大,导致调用效率不高。

3、DCL

public class SingleInstance {
    private SingleInstance() {}
    private static volatile SingleInstance INSTANCE;
    public static SingleInstance getInstance() {
        if (INSTANCE == null) {
            synchronized (SingleInstance.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleInstance();
                }
            }
        }
        return INSTANCE;
    }
}

优点:线程安全、调用效率高、能延迟加载。

4、匿名内部类

public class SingleInstance {
    private SingleInstance() {}
    private static final class HolderClass{
        private static final SingleInstance INSTANCE=new SingleInstance();
    }
    public static SingleInstance getInstance() {
        return HolderClass.INSTANCE;
    }
}

优点:线程安全,调用效率高,可以延时加载。

三、DDL单例为什么要加volatile

我们把上面写的DDL单例拿过来,加入不加volatile,如下:

public class SingleInstance {
    private SingleInstance() {}
    private static SingleInstance INSTANCE;
    public static SingleInstance getInstance() {
        if (INSTANCE == null) {
            synchronized (SingleInstance.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleInstance();
                }
            }
        }
        return INSTANCE;
    }
}

当 INSTANCE = new SingleInstance() 创建实例对象时,并不是原子操作,它是分三步来完成的:

  1. 创建内存空间。
  2. 执行构造函数,初始化(init)
  3. 将INSTANCE引用指向分配的内存空间

上述正常步骤按照1–>2–>3来执行的,但是,我们知道,JVM为了优化指令,提高程序运行效率,允许指令重排序。正是有了指令重排序的存在,那么就有可能按照1–>3–>2步骤来执行,这时候,当线程a执行步骤3完毕,在执行步骤2之前,被切换到线程b上,这时候instance判断为非空,此时线程b直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。

synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)。

volatile关键字其中一个作用就是禁止指令重排序,所以DCL单例必须要加volatile

volatile作用:

  1. 保证被修饰的变量对所有线程的可见性。
  2. 禁止指令重排序优化。
发布了79 篇原创文章 · 获赞 147 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/104843525