单例模式(Singleton Pattern)

单例模式(Singleton Pattern)

单例模式是一种创建型设计模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点;单例模式通常用于控制资源的共享访问,例如数据库连接、线程池、日志对象等。

代码示例

1. 懒汉式单例(Lazy Initialization)

懒汉式单例在第一次调用时创建实例,适合资源敏感的场景。

/**
 * 懒汉式单例:线程不安全
 *
 * @author Viices Cai
 * @time 2020/7/5
 */
public class LazySingleton {
    // 私有静态变量,用于保存唯一实例
    private static LazySingleton instance;
    
    // 私有构造方法,防止外部直接创建实例
    private LazySingleton() { }
    
    // 公共静态方法,提供全局访问点
    public static LazySingleton getInstance() {
        if (instance == null) {
            // 第一次调用时创建实例
            instance = new LazySingleton();
        }
        
        return instance;
    }
}
  • 特点:延迟初始化,适合资源敏感的场景,但需要处理线程安全问题。

2.饿汉式单例(Eager Initialization)

饿汉式单例在类加载时创建实例,适合资源不敏感的场景。

/**
 * 饿汉式单例
 *
 * @author Viices Cai
 * @time 2020/7/5
 */
public class EagerSingleton {
    // 私有静态变量,类加载时创建实例
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有构造方法,防止外部直接创建实例
    private EagerSingleton(){ }

    // 公共静态方法,提供全局访问点
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  • 类加载时初始化,适合资源不敏感的场景,线程安全。

3.静态块式单例(Static-Block Singleton)

饿汉式单例的另一种实现方式。

/**
 * 静态块式单例:同饿汉式
 *
 * @author Viices Cai
 * @time 2022/1/13
 */
public class StaticBlockSingleton {
    // 私有静态变量,用于保存唯一实例
    private static StaticBlockSingleton instance;
    
    // 私有构造方法,防止外部直接创建实例
    private StaticBlockSingleton() { }

    static { // 静态块,创建实例
        try {
            instance = new StaticBlockSingleton();

        } catch (Exception e) {
            throw new RuntimeException("Exception occured in creating singleton instance.");
        }
    }

    // 公共静态方法,提供全局访问点
    public static StaticBlockSingleton getInstance() {
        return instance;
    }
}

4. 同步锁式单例(Synchronized-Locked Singleton)

在懒汉式的基础上解决了其线程不安全的问题,但是加锁也带来了额外的开销。

/**
 * 同步锁模式
 *
 * @author Viices Cai
 * @time 2020/7/5
 */
public class LockSingleton {
    // 私有静态变量,用于保存唯一实例
    private static LockSingleton instance;
    
    // 私有构造方法,防止外部直接创建实例
    private LockSingleton() {}
    
    // 在原有的访问方法基础上加锁,避免线程抢占
    public synchronized static LockSingleton getInstance() {
        if (instance == null) {
            instance = new LockSingleton();
        }

        return instance;
    }
}

5.双重锁定式单例(Double-Checked Locking Singleton)

双重检查模式,在同步锁的实现基础上进行了改进。两次判断:第一次是为了避免重复创建实例;第二次是为了进行同步,避免多线程问题,使得其线程安全,且在多线程下能保持高性能。

/**
 * 双重锁定式单例
 *
 * @author Viices Cai
 * @time 2020/7/5
 */
public class DoubleCheckLockingSingleton {
    // 使用 volatile 关键字确保多线程环境下的可见性
    private volatile static DoubleCheckLockingSingleton instance;
    
    // 私有构造方法,防止外部直接创建实例
    private DoubleCheckLockingSingleton() { }

    // 公共静态方法,提供全局访问点
    public static DoubleCheckLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLockingSingleton.class) { // 加锁
                if (instance == null) { // 双重检查
                    instance = new DoubleCheckLockingSingleton();
                }
            }
        }

        return instance;
    }
}
  • 创建过程解析:
    1. 分配内存。
    2. .初始化对象:调用构造器。
    3. 指向刚分配的地址。
      • 若发生了指令重排序:即由于CPU调度问题执行顺序可能无法按照我们理想的情况进行,如:线程A执行了1、3此时未执行2,但是线程B进入了发现存在内存地址,但实际上对象并不存在。
      • 必须使用volatile避免重排序。
        • 禁止进行指令重排序。

6.枚举单例(Enum Singleton)

推荐使用,天然支持线程安全和防止反射攻击。

/**
 * 枚举式单例
 *
 * @author Viices Cai
 * @time 2020/7/5
 */
public enum EnumSingleton {
    INSTANCE; // 单例实例

    // 示例方法
    public void showMessage() {
        System.out.println("Hello, World!");
    }
}

应用场景

  1. 资源共享

    当需要全局共享一个资源(如数据库连接、线程池、配置文件等)时,可以使用单例模式。

  2. 控制实例数量

    当需要严格控制一个类的实例数量(只能有一个实例)时,可以使用单例模式。

  3. 延迟初始化

    当需要在第一次使用时才创建实例时,可以使用懒汉式单例。

在 Java 中的应用

  • Runtime 类:Java 的 Runtime 类是一个典型的单例,用于管理应用程序的运行环境。
  • Spring 框架:Spring 中的 Bean 默认是单例的,通过容器管理单例对象的生命周期。

优缺点

  • 优点:

    1. 全局唯一实例

      确保一个类只有一个实例,避免资源冲突。

    2. 延迟初始化

      懒汉式单例可以在第一次使用时才创建实例,节省资源。

    3. 全局访问点

      通过静态方法提供全局访问点,方便调用。

  • 缺点:

    1. 难以扩展

      单例模式通常难以扩展,因为它的构造方法是私有的。

    2. 隐藏依赖

      单例模式可能会掩盖不良设计, 比如程序各组件之间耦合过多。

    3. 线程安全问题

      懒汉式单例在多线程环境下需要额外处理线程安全问题。

总结

单例模式是一种简单但强大的设计模式,适用于需要全局唯一实例的场景。它通过私有化构造方法和提供全局访问点,确保一个类只有一个实例。单例模式在资源共享、延迟初始化和控制实例数量等场景中非常有用。然而,单例模式也存在扩展性差、隐藏依赖和线程安全问题等缺点,需要根据具体场景谨慎使用。在Java中,单例模式广泛应用于 Runtime 类和 Spring 框架中。