Skip to content

单例模式

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。

核心特点

  1. 唯一实例:整个应用程序中只能存在一个实例
  2. 全局访问:提供全局访问点来获取这个实例
  3. 自我管理:类自己负责创建和管理自己的实例

基本结构

  • 私有构造函数(防止外部创建实例)
  • 私有静态变量(存储唯一实例)
  • 公有静态方法(提供全局访问点)

饿汉式(Eager Initialization)

优点:简单、线程安全 缺点:可能浪费内存(不管用不用都创建)

java
// ===== 1. 饿汉式单例 =====
class EagerSingleton {
    // Java 的类加载机制确保每个类在 JVM 中只会被加载一次
    // 类加载时就创建实例 这个加载式项目启动加载一次 所以是单例
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    private EagerSingleton() {
        System.out.println("饿汉式单例创建");
    }
    
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

懒汉式(Lazy Initialization)

优点:按需创建,节省内存 缺点:基础版本线程不安全

java
// ===== 2. 懒汉式单例(线程不安全)=====
class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        System.out.println("懒汉式单例创建");
    }
    
    // 这个只有访问时才会初始化LazySingleton
    // 未初始化时,如果并发访问时多个线程进入此方法,这时候都会去new LazySingleton(); 线程不安全
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

线程安全懒汉式

优点:线程安全 缺点:每次获取实例都要同步,性能差

java
// ===== 3. 线程安全的懒汉式 =====
class ThreadSafeLazySingleton {
    private static ThreadSafeLazySingleton instance;
    
    private ThreadSafeLazySingleton() {
        System.out.println("线程安全懒汉式单例创建");
    }
    
    // synchronized 锁住
    // 其实只要锁住初始化就可以保证单例模式了,现在不管时初始化还是获取instance都被锁住了造成时间上的浪费
    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
    }
}

双重检查锁(Double-Checked Locking)

优点:延迟加载 + 线程安全 + 性能好 缺点:实现复杂,需要 volatile

java
// ===== 4. 双重检查锁 =====
class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance;
    
    private DoubleCheckSingleton() {
        System.out.println("双重检查锁单例创建");
    }
    
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

为什么双重检查锁需要 volatile?

问题:指令重排序

java
instance = new Singleton(); // 这行代码实际上包含3个步骤:
// 1、分配内存空间
// 2、初始化对象
// 3、将引用指向内存空间
// 危险的重排序:编译器可能会将步骤2和3重排序

导致的问题

java
// 线程A执行到这里
instance = new Singleton(); // 已经赋值但还没初始化完成,原始时这里重排序了,2、3布置置换了,所以先赋值,在初始化

// 线程B同时执行
if (instance == null) { // false!因为已经赋值了
    // 跳过同步块
}
return instance; // 返回一个没有完全初始化的对象!

volatile 的作用

shell
# volatile 是 Java 中的一个关键字,用来解决多线程环境下的可见性和有序性问题。
# 1、禁止重排序:确保初始化完成后才赋值
# 2、保证可见性:一个线程的修改立即对其他线程可见

静态内部类(推荐)

优点:延迟加载 + 线程安全 + 实现简单 缺点:无明显缺点

java
// ===== 5. 静态内部类(推荐)=====
class StaticInnerSingleton {
    private StaticInnerSingleton() {
        System.out.println("静态内部类单例创建");
    }
    
    private static class SingletonHolder {
        private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }
    
    public static StaticInnerSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

对象创建: 只在 SingletonHolder 类第一次加载时创建,仅此一次

后续调用: 返回的都是同一个对象的引用

内存开销: 只有一个 Singleton 实例存在于堆中

如有转载或 CV 的请标注本站原文地址

访客数 --| 总访问量 --