MST

星途 面试题库

面试题:Java编程中常见的反模式之单例模式误用及重构

在Java中,单例模式被广泛使用,但有时会出现误用的情况。请举例说明一种单例模式在多线程环境下可能出现的反模式,并阐述如何使用双重检查锁定机制进行重构,同时说明为什么这种重构是有效的。
15.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

单例模式在多线程环境下的反模式示例

在多线程环境中,简单的单例模式实现可能出现问题。例如,如下代码:

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

在多线程情况下,当多个线程同时进入 if (instance == null) 判断时,可能会创建多个实例,这违背了单例模式的初衷,这就是一种反模式。

使用双重检查锁定机制重构

public class Singleton {
    // volatile关键字确保可见性和禁止指令重排
    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;
    }
}

重构有效的原因

  1. 第一次检查if (instance == null) 用于避免不必要的同步开销。当实例已经被创建后,后续线程进入 getInstance() 方法时,直接返回实例,无需进入同步块。
  2. 同步块synchronized (Singleton.class) 确保在同一时间只有一个线程能够进入此块,从而避免多个线程同时创建实例。
  3. 第二次检查:在同步块内再次检查 if (instance == null),是因为当第一个线程创建实例后,其他等待在同步块外的线程会再次获得进入同步块的机会。如果没有第二次检查,这些线程仍然会创建新的实例。
  4. volatile关键字volatile 修饰 instance 变量,确保当一个线程修改 instance 后,其他线程能立即看到修改后的结果,同时禁止指令重排。因为 instance = new Singleton(); 这行代码不是原子操作,可能会出现指令重排的情况,volatile 关键字能防止这种情况发生,保证单例模式在多线程环境下的正确性。