MST

星途 面试题库

面试题:Java单例模式在资源管理中如何保证线程安全

请详细阐述在Java中,单例模式用于资源管理时,常见的保证线程安全的实现方式及其原理,并对比它们的优缺点。
41.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

饿汉式

  • 实现方式:在类加载时就创建单例实例。
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  • 原理:类加载机制保证了INSTANCE只会被创建一次,由于类加载由JVM保证线程安全,所以饿汉式天生线程安全。
  • 优点:实现简单,线程安全,在类加载时就完成初始化,不会存在多线程并发访问的问题。
  • 缺点:如果单例实例在整个应用生命周期中不一定会被使用,那么类加载时就创建实例会造成资源浪费。

懒汉式(线程不安全)

  • 实现方式:在第一次调用getInstance方法时才创建实例。
public class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {}
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}
  • 原理:延迟加载,只有在需要时才创建实例。
  • 优点:实现简单,延迟加载,避免了不必要的资源浪费。
  • 缺点:线程不安全,在多线程环境下可能会创建多个实例。

懒汉式(线程安全,同步方法)

  • 实现方式:对getInstance方法加synchronized关键字。
public class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}
  • 原理synchronized关键字保证了同一时间只有一个线程能进入getInstance方法,从而避免了多线程并发创建实例的问题。
  • 优点:实现相对简单,保证了线程安全。
  • 缺点:性能问题,每次调用getInstance方法都需要进行同步,即使实例已经创建,会造成不必要的性能开销。

双重检查锁(DCL)

  • 实现方式:两次检查INSTANCE是否为null,并在同步块内再次检查。
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;
    }
}
  • 原理:第一次if (INSTANCE == null)检查是为了避免不必要的同步,当INSTANCE已经创建时,直接返回实例。同步块内的第二次if (INSTANCE == null)检查是为了防止多个线程同时通过第一次检查,在同步块内再次创建实例。volatile关键字保证了INSTANCE的可见性,防止指令重排导致的初始化不完全问题。
  • 优点:既保证了线程安全,又提高了性能,只有在第一次创建实例时会进行同步操作。
  • 缺点:实现相对复杂,需要正确使用volatile关键字,并且在早期JVM版本中可能存在指令重排问题导致单例创建失败。

静态内部类

  • 实现方式:使用静态内部类来延迟加载单例实例。
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  • 原理:静态内部类只有在调用getInstance方法时才会被加载,类加载机制保证了INSTANCE只会被创建一次,从而实现了延迟加载和线程安全。
  • 优点:实现相对简单,延迟加载,线程安全,利用了类加载机制保证单例的唯一性。
  • 缺点:在一些特殊场景下,如序列化和反射攻击时,可能会破坏单例模式。

枚举

  • 实现方式:使用枚举类型来实现单例。
public enum Singleton {
    INSTANCE;
    // 可以在这里添加其他方法和属性
}
  • 原理:枚举类型在Java中是线程安全的,并且只会被实例化一次。
  • 优点:实现简单,线程安全,天然防止反射和序列化攻击。
  • 缺点:不太符合传统的单例模式实现方式,在某些情况下可能不够灵活,例如不能继承其他类。