Java 单例模式(Singleton)

Java 单例模式(Singleton)

一、什么是单例

保证一个类在整个程序中,永远只有唯一一个实例对象,全局共用这一个对象。

适用场景:工具类、数据库连接池、线程池、配置管理器等,频繁创建销毁浪费资源。

二、核心实现要点

  1. 私有构造方法 private:禁止外部 new 创建对象
  2. 类内部持有唯一静态实例
  3. 提供静态方法,对外获取该唯一实例

三、常见 4 种写法

1. 饿汉式(最简单,线程安全)

类加载时直接创建实例,天生线程安全,缺点:类一加载就占用内存,不用也会创建。

java 复制代码
public class Singleton {
    // 类加载直接初始化
    private static final Singleton INSTANCE = new Singleton();
    // 私有构造,禁止new
    private Singleton(){}
    // 对外获取实例
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

使用:Singleton s = Singleton.getInstance();

2. 懒汉式(非线程安全,不推荐)

调用方法时才创建,延迟加载;多线程并发会创建多个对象。

java 复制代码
public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

3. 双重检查锁 DCL(Double-Check Locking,工作常用)

延迟加载 + 线程安全,性能好。

必须加 volatile 防止指令重排导致空指针。

java 复制代码
public class Singleton {
    // volatile 禁止指令重排
    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;
    }
}

4. 静态内部类(最优写法,推荐)

懒加载、天然线程安全、无锁、性能高。

外部类加载不会初始化内部类,只有调用 getInstance() 才创建对象。

java 复制代码
public class Singleton {
    private Singleton(){}
    // 静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

5. 枚举单例(最安全,防反射、序列化破坏)

Effective Java 推荐,彻底杜绝反射、序列化破解单例。

java 复制代码
public enum Singleton {
    INSTANCE;
    // 业务方法
    public void doSomething(){
        System.out.println("单例执行");
    }
}
// 使用
Singleton.INSTANCE.doSomething();

四、破坏单例的两种方式 & 规避

  1. 反射破坏
    反射可以调用私有构造创建新对象。
    解决:枚举单例 / 构造中判断实例非空直接抛异常。
java 复制代码
private Singleton(){
    if(instance != null){
        throw new RuntimeException("禁止反射创建实例");
    }
}
  1. 序列化破坏
    对象序列化再反序列化会生成新对象。
    解决:添加 readResolve() 方法返回已有实例。
java 复制代码
private Object readResolve(){
    return instance;
}

五、各方式对比总结

写法 懒加载 线程安全 优点 缺点
饿汉式 简单安全 提前占用内存
普通懒汉 延迟加载 多线程不安全
DCL双重检查 延迟、高性能 需volatile,写法复杂
静态内部类 简洁、无锁、高效 无法传参构造
枚举 防反射序列化,极简 不支持有参构造

六、面试高频问题

  1. 为什么 DCL 要加 volatile?
    new 对象 分三步:分配内存→初始化对象→赋值引用。volatile 禁止指令重排,避免其他线程拿到半初始化对象。
  2. 静态内部类为什么线程安全?
    静态变量初始化由 JVM 保证同步,只会执行一次。
  3. 为什么枚举是最安全单例?
    底层禁止反射调用构造,序列化不会生成新实例,无漏洞。