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:枚举单例是如何限制实例个数的?
枚举单例通过枚举的机制天然地保证:
- 枚举类的每一个枚举实例(如单例对象)都在 类加载阶段 就完成初始化,并且整个应用程序中只有一个实例。
- 枚举类型底层由 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:枚举单例在创建时是否有并发问题?
枚举单例天然线程安全,因为:
- 枚举类型的初始化由 JVM 保证,是在类加载时完成的。
- 类加载过程是线程安全的,JVM 使用了类加载的同步机制,保证枚举单例的初始化不会因多线程而发生竞争。
举例:
即使多个线程同时调用 SingletonEnum.INSTANCE
,它们都会得到在类加载阶段构造好的唯一对象,无需额外同步。
问题 3:枚举单例能否被反射破坏单例?
不会! 枚举类型的结构特殊,无法被反射破坏单例。这是因为:
- 枚举的构造器是私有的,并且其底层会检测反射调用。
- 如果尝试通过反射显式调用枚举类的构造器,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:枚举单例能否被反序列化破坏单例?
枚举单例天然具备防止反序列化破坏的特性,原因是:
Enum
类型的序列化机制是由 JVM 内部实现的,不走普通的对象序列化流程。- 反序列化枚举对象时,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;
}
}