单例模式

90 阅读3分钟

在Java 5之前,创建Singleton模式需要手动处理多线程环境下的同步问题,以确保实例的唯一性。以下是几种常见的实现方法:

1. 饿汉式(Eager Initialization)

饿汉式在类加载时就创建单例实例,线程安全,但无法懒加载。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
        // 私有构造函数
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

2. 懒汉式(Lazy Initialization)

懒汉式在第一次调用时创建实例,但在多线程环境下不安全。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        // 私有构造函数
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3. 线程安全的懒汉式(Thread-Safe Lazy Initialization)

使用sychronized关键字保证线程安全,但性能较差。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        // 私有构造函数
    }
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

4. 双重检查锁定(Double-Checked Locking)

结合了懒汉式和饿汉式的优点,保证了线程安全和性能。

public class Singleton {
    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;
    }
}

5. 静态内部类(Static Inner Class)

利用类加载机制,保证线程安全且支持懒加载。

public class Singleton {
    private Singleton() {
        // 私有构造函数
    }
    
    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

说明

  • 饿汉式:简单但不支持懒加载。
  • 懒汉式:懒加载但线程不安全。
  • 线程安全的懒汉式:保证线程安全但性能较差。
  • 双重检查锁定:线程安全且性能较好,但实现复杂。
  • 静态内部类:线程安全、性能好且实现简单。

以下是带有测试代码的完整示例,展示了如何在Java 5之前使用双重检查锁定(Double-Checked Locking)实现Singleton模式,并在main方法中进行测试:

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

    // 示例方法
    public void someMethod() {
        System.out.println("Hello from Singleton!");
    }

    // main方法,测试Singleton
    public static void main(String[] args) {
        // 获取Singleton实例并调用方法
        Singleton singleton1 = Singleton.getInstance();
        singleton1.someMethod();

        // 验证多次调用getInstance()是否返回同一个实例
        Singleton singleton2 = Singleton.getInstance();
        System.out.println("Are both instances the same? " + (singleton1 == singleton2));
    }
}

解释

  1. volatile关键字

    • 确保instance变量的可见性和有序性,防止指令重排序。
  2. 双重检查锁定

    • 第一次检查:避免不必要的同步。
    • 第二次检查:确保线程安全地创建实例。
  3. someMethod方法

    • 示例方法,模拟单例类中的业务逻辑。
  4. main方法

    • 测试代码,通过getInstance()方法获取单例实例,并调用someMethod()方法。
    • 验证多次调用getInstance()方法是否返回同一个实例。

运行结果

Hello from Singleton!
Are both instances the same? true

使用Enum实现Singleton模式在Java中非常简单和高效。下面是一个完整的示例,展示如何使用枚举来实现Singleton:

public enum Singleton {
    INSTANCE;

    // 可以定义其他方法和变量
    public void someMethod() {
        System.out.println("Hello from Singleton!");
    }
}

// 使用示例
public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        singleton.someMethod();
    }
}

说明

  1. 定义Singleton枚举

    • Singleton枚举定义了一个单一的实例INSTANCE
    • 可以在枚举中添加方法和变量,与普通类一样。
  2. 使用Singleton

    • 在客户端代码中,通过Singleton.INSTANCE来获取唯一实例。
    • 可以调用实例的方法,如someMethod()

优点

  • 线程安全:枚举类型的线程安全性由JVM保证,无需额外的同步机制。
  • 防止反序列化:枚举类型防止反序列化创建新的实例。
  • 防止反射攻击:反射无法破解枚举的单例模式。