MST

星途 面试题库

面试题:Java单例模式中双重检查锁定实现线程安全的原理

请详细阐述在Java中,使用双重检查锁定(Double - Checked Locking)来实现单例模式线程安全的具体原理,包括为什么要进行两次检查以及volatile关键字在此处的作用。
34.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 双重检查锁定实现单例模式线程安全原理
    • 双重检查锁定(Double - Checked Locking)是一种在多线程环境下实现高效单例模式的技术。它的核心思想是在获取单例实例时,先进行一次快速的检查,判断实例是否已经创建,如果已经创建则直接返回,避免不必要的同步开销。如果实例未创建,则进入同步块进行创建操作。在同步块内部,再次检查实例是否已经创建,这就是所谓的双重检查。
  2. 为什么要进行两次检查
    • 第一次检查:主要目的是提高效率。在多线程环境下,很多线程可能同时调用获取单例实例的方法。如果没有第一次检查,每次调用都需要进入同步块,而同步操作是比较消耗性能的。通过第一次检查,大部分情况下(实例已经创建),线程可以直接返回实例,无需进入同步块,从而大大提高了性能。
    • 第二次检查:当多个线程通过第一次检查并进入同步块时,由于同步块一次只能允许一个线程进入,所以第一个进入同步块的线程会创建实例,而后续进入同步块的线程如果没有第二次检查,就会再次创建实例,导致单例模式失效。第二次检查确保了即使多个线程同时通过第一次检查进入同步块,也只有一个线程会真正创建实例。
  3. volatile关键字在此处的作用
    • 在Java中,volatile关键字主要有两个作用:禁止指令重排序和保证内存可见性。
    • 禁止指令重排序:在创建对象时,JVM可能会进行指令重排序优化。正常情况下创建对象的步骤是:①分配内存空间;②初始化对象;③将对象引用指向分配的内存空间。但是JVM可能会将②和③的顺序重排为①③②。如果发生这种重排,当一个线程执行到③时,另一个线程通过第一次检查发现实例不为空(因为引用已经指向内存空间),就会直接返回这个未初始化完成的实例,导致程序出错。使用volatile关键字修饰单例实例变量,可以禁止这种指令重排序,保证按照①②③的顺序执行。
    • 保证内存可见性:当一个线程修改了volatile修饰的变量后,会立即将修改后的值刷新到主内存中,其他线程在读取该变量时,会从主内存中获取最新的值。在单例模式中,保证了其他线程能够及时看到单例实例的创建,避免获取到旧的或者未初始化完成的实例。

示例代码如下:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}