面试必问题:单例模式的5种写法
单例模式主要有以下几种分类:
- 饿汉式单例:在类加载时就创建单例对象,优点是实现简单,线程安全,缺点是如果单例对象在程序运行过程中一直未被使用,会造成资源浪费。例如:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}

- 懒汉式单例:在第一次调用 getInstance 方法时才创建单例对象,优点是可以延迟加载,节省资源,但是在多线程环境下需要考虑线程安全问题。例如:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

- 双重检查锁单例:在懒汉式单例的基础上,通过双重检查锁机制来提高性能并保证线程安全。例如:
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;
}
}

- 静态内部类单例:利用静态内部类的特性来实现单例,既保证了线程安全,又实现了延迟加载。例如:
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

- 枚举单例:通过枚举类型来实现单例,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。例如:
public enum Singleton {
INSTANCE;
public void doSomething() {
// 单例对象的方法
}
}

在多线程环境下,保证单例模式的线程安全可以采用以下几种方法:
- 使用 synchronized 关键字:在获取单例对象的方法上添加 synchronized 关键字,如懒汉式单例中的 getInstance 方法,这样可以保证在同一时刻只有一个线程能够访问该方法,从而避免多个线程同时创建单例对象。但是这种方式会降低性能,因为每次调用该方法都需要获取锁。
- 双重检查锁机制:在懒汉式单例的基础上,通过双重检查锁机制来减少同步的开销。在第一次检查实例是否为 null 时,如果不为 null,则直接返回实例,不需要获取锁;只有在第一次检查为 null 时,才进入同步块再次检查并创建实例。同时,需要将实例变量声明为 volatile,以保证可见性和禁止指令重排序。
- 静态内部类:利用静态内部类的特性,在类加载时不会立即初始化内部类,只有在第一次调用 getInstance 方法时,才会加载并初始化内部类,从而创建单例对象。由于类加载过程是线程安全的,所以这种方式可以保证单例的线程安全,并且实现了延迟加载。
- 枚举:枚举类型在 Java 中是天然的单例,因为 Java 虚拟机在加载枚举类时会保证只有一个实例存在,并且枚举类的实例是线程安全的,所以可以通过枚举来实现单例模式,避免了复杂的线程安全处理。