MST

星途 面试题库

面试题:Java中常见的单例模式实现方式及区别

请列举出Java中至少两种常见的单例模式实现方式,并阐述它们在线程安全性、性能、资源占用等方面的区别。
38.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

饿汉式

  • 实现方式:在类加载时就创建实例,代码如下:
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  • 线程安全性:线程安全,因为类加载由 JVM 保证线程安全,在类加载时实例就已创建好,后续调用 getInstance 不会再创建新实例。
  • 性能:性能较好,因为 getInstance 方法没有任何锁操作,直接返回已创建的实例。
  • 资源占用:可能会造成资源浪费,如果这个单例对象一直没有使用,但在类加载时就已创建。

懒汉式(线程不安全)

  • 实现方式:在第一次调用 getInstance 方法时才创建实例,代码如下:
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 线程安全性:线程不安全,在多线程环境下,当多个线程同时判断 instance == null 时,可能会创建多个实例。
  • 性能:性能较好,只有在需要时才创建实例。但在多线程环境下,需额外处理线程安全问题,可能会影响性能。
  • 资源占用:资源利用率高,只有在真正使用时才占用资源创建实例。

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

  • 实现方式:通过给 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)

  • 实现方式:在 synchronized 块内再进行一次 null 判断,代码如下:
public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 线程安全性:线程安全,volatile 关键字保证了 instance 变量的可见性,避免指令重排序问题,同时双重 null 判断和 synchronized 块保证了在多线程环境下只会创建一个实例。
  • 性能:性能较好,只有在第一次创建实例时会有锁操作,后续调用 getInstance 方法无需获取锁,直接返回已创建的实例。
  • 资源占用:资源利用率高,延迟加载,只有在需要时才创建实例。

静态内部类

  • 实现方式:利用静态内部类的特性,在外部类被加载时,静态内部类不会被加载,只有在调用 getInstance 方法时,静态内部类才会被加载并创建实例,代码如下:
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  • 线程安全性:线程安全,类加载由 JVM 保证线程安全,静态内部类在第一次使用时才会被加载,且只会加载一次,保证了实例的唯一性。
  • 性能:性能较好,getInstance 方法没有任何锁操作,直接返回已创建的实例。
  • 资源占用:资源利用率高,延迟加载,只有在调用 getInstance 方法时才会占用资源创建实例。