目录

单例设计模式

单例设计模式

在 Java 中, 单例模式 (Singleton Pattern)是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在实现单例模式时,有几种不同的加载方式,主要包括 懒汉式 (Lazy Initialization)和 饿汉式 (Eager Initialization)。

单例模式的加载方式

  1. 饿汉式单例 (Eager Initialization)
  2. 懒汉式单例 (Lazy Initialization)
  3. 双重检查锁定(Double-Checked Locking) 单例
  4. 静态内部类单例 (Bill Pugh Singleton)
  5. 枚举单例 (Enum Singleton)

下面会详细介绍这些实现方式。


1. 饿汉式单例 (Eager Initialization)

饿汉式单例的特点是,在类加载时就创建单例对象,确保线程安全,不需要任何同步机制。这种方式在类加载时就创建实例,因此无法进行延迟加载。

实现方式:

java

public class Singleton {
    // 在类加载时就创建实例
    private static final Singleton instance = new Singleton();
    
    // 私有化构造函数
    private Singleton() {}
    
    // 提供公共访问方法
    public static Singleton getInstance() {
        return instance;
    }
}

java

解释:
  • 线程安全 :由于实例是在类加载时就创建的,确保了线程安全。
  • 缺点 :即使实例从未使用,类加载时实例也会被创建,浪费了资源。

2. 懒汉式单例 (Lazy Initialization)

懒汉式单例的特点是实例是在第一次调用 getInstance() 时创建的,采用延迟加载。通常需要加锁以保证线程安全。

实现方式:

java

public class Singleton {
    // 实例对象,未初始化
    private static Singleton instance;
    
    // 私有化构造函数
    private Singleton() {}
    
    // 提供公共访问方法
    public static synchronized Singleton getInstance() {
        // 如果实例为空,则创建实例
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

java

解释:
  • 延迟加载 :实例只会在第一次使用时被创建,节省了资源。
  • 线程安全 :使用了 synchronized 关键字来保证线程安全。
  • 缺点 :每次调用 getInstance() 都会进行同步,性能较差。

3. 双重检查锁定(Double-Checked Locking) 单例

双重检查锁定的懒汉式单例,通过两次 if (instance == null) 检查来减少加锁的次数,提升性能。第一层检查不加锁,第二层检查加锁后创建实例。

实现方式:

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;
    }
}

java

解释:
  • 延迟加载 :实例只会在第一次调用时被创建。
  • 性能优化 :第一次检查不加锁,只有在实例为空时才进行加锁,减少了加锁的开销。
  • 线程安全 :使用 synchronized 保证线程安全,且 volatile 关键字确保实例初始化的正确性。
  • 缺点 :代码较为复杂,需要理解双重检查锁定的实现原理。

4. 静态内部类单例 (Bill Pugh Singleton)

静态内部类单例是一种非常高效且线程安全的单例实现方式,它利用了 类加载机制 来保证线程安全,且没有加锁的性能问题。

实现方式:

java

public class Singleton {
    // 静态内部类实现单例
    private static class SingletonHelper {
        // 静态初始化器,由JVM来保证线程安全
        private static final Singleton INSTANCE = new Singleton();
    }
    
    // 私有化构造函数
    private Singleton() {}
    
    // 提供公共访问方法
    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

java

解释:
  • 线程安全 :静态内部类在第一次加载时才会被初始化,而类加载是线程安全的。
  • 性能 :静态内部类只有在首次使用时才会被加载,避免了不必要的同步操作。
  • 优点 :没有同步的开销,且实现简单。
  • 缺点 :需要理解类加载的机制。

5. 枚举单例 (Enum Singleton)

使用 enum 实现单例模式是最简单也是最安全的方式。由于 Java 枚举的特性,JVM 会保证枚举实例的唯一性,同时会确保线程安全。根据 Joshua Bloch (《Effective Java》的作者)的建议,这是实现单例模式的最佳方式。

实现方式:

java

public enum Singleton {
    INSTANCE;
    
    // 可以添加其他方法
    public void someMethod() {
        System.out.println("Hello, Singleton!");
    }
}
解释:
  • 线程安全 :Java 枚举类本身就是线程安全的。
  • 实现简单 :使用枚举类避免了许多复杂的代码和同步问题。
  • JVM 保证 :JVM 在加载枚举类型时会保证它只会加载一次,并且是线程安全的。
  • 优点 :最简单,最安全,且是 JDK 推荐的单例实现方式。
  • 缺点 :无法延迟实例化,单例是预先创建的。

总结:懒汉式 vs 饿汉式

  1. 懒汉式单例 (Lazy Initialization):

    • 优点 :延迟加载,只有在需要时才创建实例,节省资源。
    • 缺点 :需要加锁,性能差,特别是在多线程环境下。
    • 线程安全 :需要使用 synchronized 或其他同步机制来保证线程安全。
  2. 饿汉式单例 (Eager Initialization):

    • 优点 :简单实现,线程安全,类加载时即创建实例。
    • 缺点 :即使没有使用该实例,也会创建,浪费资源。
    • 线程安全 :类加载时创建实例,线程安全。

推荐使用方式

  • 枚举单例 是最推荐的单例实现方式,简单、安全、可靠,避免了所有其他方式中的问题。
  • 如果需要延迟加载且在多线程环境中使用,可以考虑 静态内部类单例双重检查锁定 单例,它们是线程安全且性能较好的方案。