MST

星途 面试题库

面试题:Java单例模式下不同线程安全方案的性能对比

在Java单例模式中,存在多种线程安全的实现方案,如饿汉式、懒汉式(线程安全版本)、双重检查锁定、静态内部类以及枚举等方式。请从性能角度对这些方案进行详细对比,并说明在不同应用场景下应如何选择。
29.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

性能对比

  1. 饿汉式
    • 性能:在类加载时就创建实例,不存在线程同步问题,所以性能上没有因同步带来的额外开销。但如果该单例实例一直未被使用,就会造成内存浪费。
    • 示例代码
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  1. 懒汉式(线程安全版本)
    • 性能:在第一次调用getInstance方法时创建实例,解决了饿汉式可能的内存浪费问题。然而,由于使用synchronized关键字修饰getInstance方法,每次调用该方法都需要进行线程同步,这在高并发场景下会成为性能瓶颈。
    • 示例代码
public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
  1. 双重检查锁定
    • 性能:既实现了延迟加载,又避免了不必要的同步。在第一次检查instance == null时,多数情况下可以直接返回实例,只有在实例未创建时才会进入同步块。并且在同步块内再次检查instance == null,确保多线程环境下只创建一次实例。这种方式在高并发场景下性能较好。
    • 示例代码
public class DoubleCheckedLockingSingleton {
    private volatile static DoubleCheckedLockingSingleton instance;
    private DoubleCheckedLockingSingleton() {}
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}
  1. 静态内部类
    • 性能:利用了类加载机制实现延迟加载,并且保证线程安全。在外部类被加载时,静态内部类不会被加载,只有在调用getInstance方法时,静态内部类才会被加载并创建实例。这种方式既保证了延迟加载,又没有额外的同步开销,性能较好。
    • 示例代码
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}
    private static class InnerClass {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.instance;
    }
}
  1. 枚举
    • 性能:枚举实现单例模式非常简洁,并且由JVM保证其线程安全和实例唯一性。在序列化和反序列化时也能保证单例特性,性能上没有同步等额外开销。
    • 示例代码
public enum EnumSingleton {
    INSTANCE;
    // 可以添加其他方法和属性
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

不同场景下的选择

  1. 应用场景一:对启动性能要求高,且单例实例一定会被使用
    • 选择:饿汉式。因为它在类加载时就创建实例,没有延迟加载的开销,对于启动性能有一定提升,并且不会造成内存浪费。
  2. 应用场景二:对内存比较敏感,且并发访问不高
    • 选择:懒汉式(线程安全版本)。它实现了延迟加载,能节省内存,虽然每次获取实例都有同步开销,但在并发访问不高的情况下,对性能影响不大。
  3. 应用场景三:高并发场景,且对延迟加载有要求
    • 选择:双重检查锁定或静态内部类。双重检查锁定通过减少不必要的同步,在高并发下有较好性能;静态内部类利用类加载机制保证延迟加载和线程安全,同样适用于高并发场景。
  4. 应用场景四:需要考虑序列化和反序列化的场景
    • 选择:枚举。它在序列化和反序列化时能保证单例特性,不需要额外处理。