MST

星途 面试题库

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

在Java中,单例模式是一种常用设计模式,但也容易出现反模式情况。请描述单例模式在多线程环境下可能出现的反模式场景,以及如何修正以确保其线程安全。
45.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

反模式场景

  1. 双重检查锁定(DCL)问题:在早期的Java中,使用双重检查锁定实现单例,由于指令重排序,可能会导致在对象未完全初始化时就被其他线程访问。例如:
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 这里可能发生指令重排
                }
            }
        }
        return instance;
    }
}

instance = new Singleton() 这行代码中,对象的创建可能分为三步:分配内存空间、初始化对象、将 instance 指向分配的内存地址。由于指令重排,可能会先执行 1 和 3,然后执行 2。此时,如果另一个线程进入 getInstance 方法,在第一次检查 instance == null 时,发现 instance 不为空,但实际上对象还未初始化完成,从而导致错误。

  1. 静态内部类延迟加载的错误使用:虽然静态内部类实现单例能保证线程安全,但如果在静态内部类中包含一些非线程安全的操作,比如修改共享的静态变量,就会出现问题。例如:
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
        // 假设这里有一个共享的静态变量且非线程安全操作
        private static int sharedValue = 0;
        public static void incrementSharedValue() {
            sharedValue++;
        }
    }
    private Singleton() {}
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

如果多个线程同时调用 SingletonHolder.incrementSharedValue(),由于 sharedValue 没有同步机制,会导致数据不一致。

修正方法

  1. 使用 volatile 关键字解决 DCL 问题:对 instance 加上 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;
    }
}
  1. 解决静态内部类中共享变量的线程安全问题:对共享变量的操作进行同步。例如:
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
        private static int sharedValue = 0;
        public static synchronized void incrementSharedValue() {
            sharedValue++;
        }
    }
    private Singleton() {}
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

另外,还可以使用以下方式确保单例的线程安全:

  • 饿汉式单例:在类加载时就创建实例,天然线程安全。
public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}
  • 使用 Enum 实现单例:Java 枚举类型本身就保证了线程安全和单例性。
public enum Singleton {
    INSTANCE;
    // 可以在这里添加方法
    public void someMethod() {
        System.out.println("执行方法");
    }
}