1

package org.example.sigletons;

import java.io.Serializable;

public class Singleton1 implements Serializable {
    private Singleton1(){}
    private static final Singleton1 INSTANCE = new Singleton1();
    public Singleton1 getInstance() {
        return INSTANCE;
    }
    public Object readResolve() {
        return INSTANCE;
    }
}

2

package org.example.sigletons;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public enum Singleton2 {
    INSTANCE;
    private final Properties properties;
    private Singleton2() {
        properties = new Properties();
        String configFile = "application.properties";
        System.out.println("ConfigurationManager: Initializing and loading " + configFile);
        try(InputStream inputStream = Singleton2.class.getClassLoader().getResourceAsStream(configFile)){
            if(inputStream == null){
                System.out.println("ConfigurationManager: Sorry, unable to find " + configFile);
                // 在实际应用中,这里可能抛出异常或有更复杂的错误处理
                return;
            }
            properties.load(inputStream);
            System.out.println("ConfigurationManager: Configuration loaded successfully.");
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
    public String getProperty(String key,String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }

    // 可以添加其他需要的方法,比如重新加载配置等(需要考虑线程安全)
    public void listProperties() {
        properties.forEach((key, value) -> System.out.println(key + "=" + value));
    }
}
class TestSingleton2 {
    public static void main(String[] args) {
        Singleton2 singleton2 = Singleton2.INSTANCE;
        singleton2.listProperties();
    }
}

💎 枚举单例:全面解答这些问题!📜✨

枚举单例是一种非常推荐的单例实现方式,因为它不仅简单、易用,还天然地具备线程安全和防止反序列化、反射破坏单例的能力。接下来,我们重点针对 枚举单例 来回答这些问题!


问题 1:枚举单例是如何限制实例个数的?

枚举单例通过枚举的机制天然地保证:

  1. 枚举类的每一个枚举实例(如单例对象)都在 类加载阶段 就完成初始化,并且整个应用程序中只有一个实例。
  2. 枚举类型底层由 JVM 的实现机制保证,它不像普通类那样允许通过反射或 new 额外创建实例。

示例:

public enum SingletonEnum {
    INSTANCE; // 枚举单例实例

    public void doSomething() {
        System.out.println("Doing something...");
    }
}

使用方式:

即便通过 SingletonEnum.INSTANCE 多次获取,得到的始终是同一个实例。

SingletonEnum instance1 = SingletonEnum.INSTANCE;
SingletonEnum instance2 = SingletonEnum.INSTANCE;
System.out.println(instance1 == instance2); // 输出:true

问题 2:枚举单例在创建时是否有并发问题?

枚举单例天然线程安全,因为:

  1. 枚举类型的初始化由 JVM 保证,是在类加载时完成的。
  2. 类加载过程是线程安全的,JVM 使用了类加载的同步机制,保证枚举单例的初始化不会因多线程而发生竞争。

举例:

即使多个线程同时调用 SingletonEnum.INSTANCE,它们都会得到在类加载阶段构造好的唯一对象,无需额外同步。


问题 3:枚举单例能否被反射破坏单例?

不会! 枚举类型的结构特殊,无法被反射破坏单例。这是因为:

  1. 枚举的构造器是私有的,并且其底层会检测反射调用。
  2. 如果尝试通过反射显式调用枚举类的构造器,JVM 会抛出 IllegalArgumentException

验证代码:

import java.lang.reflect.Constructor;

public class EnumReflectionTest {
    public static void main(String[] args) {
        try {
            // 获取枚举的构造器
            Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            SingletonEnum instance = constructor.newInstance(); // 反射创建枚举对象
        } catch (Exception e) {
            e.printStackTrace(); // 会抛出 IllegalArgumentException
        }
    }
}

运行结果:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects

问题 4:枚举单例能否被反序列化破坏单例?

枚举单例天然具备防止反序列化破坏的特性,原因是:

  1. Enum 类型的序列化机制是由 JVM 内部实现的,不走普通的对象序列化流程。
  2. 反序列化枚举对象时,JVM 会直接返回枚举类中的现有实例,而不是从序列化流中创建新对象。

验证代码:

import java.io.*;

public class EnumSerializationTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SingletonEnum instance1 = SingletonEnum.INSTANCE;

        // 序列化枚举对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enum_singleton.obj"));
        oos.writeObject(instance1);
        oos.close();

        // 反序列化枚举对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enum_singleton.obj"));
        SingletonEnum instance2 = (SingletonEnum) ois.readObject();

        // 判断是否破坏单例
        System.out.println(instance1 == instance2); // 输出:true,单例没有破坏
    }
}

问题 5:枚举单例属于懒汉式还是饿汉式?

枚举单例本质上属于饿汉式单例。它的特点是在类加载阶段完成初始化

  • 枚举的实例在类加载时就被创建并初始化。
  • 即使程序中从未访问过 SingletonEnum.INSTANCE,枚举实例依然会被加载。

优点:

  • 线程安全,无需为单例初始化额外编写同步代码。
  • 实现简洁,JVM 自动保证。

缺点:

  • 如果枚举实例较多,并且包含较大的初始化逻辑,会导致类加载阶段性能开销增加。

问题 6:枚举单例如果希望加入一些初始化逻辑,该如何做?

可以通过添加枚举的构造方法和静态方法来实现初始化逻辑。枚举的构造方法是私有的,可以用来在实例创建时执行初始化。

修改代码:

public enum SingletonEnum {
    INSTANCE; // 枚举单例实例

    private String configuration;

    // 枚举的构造方法
    SingletonEnum() {
        // 初始化逻辑
        configuration = "System Configuration Loaded";
    }

    public String getConfiguration() {
        return configuration;
    }
}

测试:

public class TestEnumInitialization {
    public static void main(String[] args) {
        SingletonEnum instance = SingletonEnum.INSTANCE;
        System.out.println(instance.getConfiguration()); // 输出:System Configuration Loaded
    }
}

分析:

  • 枚举类型的构造器会在类加载时调用,且只调用一次。
  • 可用枚举构造器实现单例实例的初始化逻辑。

总结

为何枚举单例完美适合单例模式?

  • 它是天生线程安全的,JVM 保障了枚举实例的唯一性。
  • 枚举实例不能通过反射或序列化破坏。
  • 枚举的初始化流程天然符合饿汉式单例的特点。

3 Double Check

https://meowrain.cn/archives/volatile-shi-xian-dan-li-mo-shi-de-shuang-zhong-suo

package cn.meowrain;

public class DoubleSingleton {
    private static volatile DoubleSingleton INSTANCE = null;
    public static DoubleSingleton getInstance() {
        if(INSTANCE != null) {
            return INSTANCE;
        }
        synchronized (DoubleSingleton.class){
            if(INSTANCE != null) {
                return INSTANCE;
            }
            INSTANCE = new DoubleSingleton();
            return INSTANCE;
        }
    }
}

4 静态内部类懒汉式创建线程安全单例

package cn.meowrain;

public class Singleton2 {
    private Singleton2(){}
    // 问题1: 属于懒汉式还是饿汉式
    private static class LazyLoader{
        static final Singleton2 INSTANCE = new Singleton2();
    }
    // 在创建的时候是否有并发问题
    public static Singleton2 getInstance() {
        return  LazyLoader.INSTANCE;
    }
}