MST

星途 面试题库

面试题:Java并发编程中双重检查锁定单例模式在多线程环境下的问题及解决方案

简述双重检查锁定单例模式在Java并发编程中的实现方式。分析在多线程环境下可能会出现的问题,特别是与指令重排序相关的问题。请给出完整的、线程安全的双重检查锁定单例模式代码,并详细解释每一步的作用。
40.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

双重检查锁定单例模式的实现方式

  1. 定义单例类: 首先定义一个类,该类持有自身的一个静态实例,并且构造函数为私有,防止外部实例化。
public class Singleton {
    // 声明静态实例
    private static Singleton instance;

    // 私有构造函数
    private Singleton() {}
  1. 提供获取实例的方法: 通过一个静态方法来获取单例实例,在方法中使用双重检查锁定机制。
    public static Singleton getInstance() {
        // 第一次检查,若实例已存在则直接返回,减少不必要的同步开销
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次检查,确保在同步块中再次确认实例是否已创建
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

多线程环境下的问题及指令重排序问题分析

  1. 指令重排序问题: 在 instance = new Singleton(); 这一步,实际上会有多个指令执行。正常顺序是:
    • 分配内存空间给 Singleton 对象。
    • 初始化 Singleton 对象。
    • instance 引用指向分配的内存空间。 但在某些情况下,编译器或处理器可能会对指令进行重排序,变成:
    • 分配内存空间给 Singleton 对象。
    • instance 引用指向分配的内存空间。
    • 初始化 Singleton 对象。 如果发生这种重排序,当一个线程执行到 instance = new Singleton(); 且执行完重排序后的前两步,此时 instance 已经不为 null 了,但对象还未初始化完成。如果另一个线程在这个时候通过 if (instance == null) 检查,就会获取到一个未初始化完全的对象,导致程序出错。

线程安全的双重检查锁定单例模式代码及解释

public class ThreadSafeSingleton {
    // 使用 volatile 关键字修饰,防止指令重排序
    private static volatile ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    // 这里的 new 操作在 volatile 关键字的作用下,不会发生指令重排序
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}
  1. volatile 关键字
    • 对于 private static volatile ThreadSafeSingleton instance; 这一行,volatile 关键字保证了 instance 变量的可见性和禁止指令重排序。当一个线程修改了 instance 的值,其他线程能立即看到这个修改。同时,保证 instance = new ThreadSafeSingleton(); 这一操作不会发生指令重排序,从而避免上述获取到未初始化完全对象的问题。
  2. 双重检查机制
    • 外层的 if (instance == null) 检查,主要是为了减少不必要的同步开销。如果 instance 已经存在,就直接返回,不需要进入同步块。
    • 内层的 if (instance == null) 检查,是在同步块内部再次确认。因为当多个线程同时通过外层检查进入同步块时,只有一个线程能创建实例,其他线程进来后发现 instance 已被创建,就直接返回。
  3. 同步块synchronized (ThreadSafeSingleton.class) 这一同步块保证了在同一时间只有一个线程能进入创建实例的代码块,从而确保了单例的唯一性。