Java 安全的单例模式详解

一、单例模式的基本概念

1.1 什么是单例模式

确保一个类只有一个实例,并提供一个全局访问点。

1.2 使用场景

  • 配置管理器

  • 数据库连接池

  • 日志记录器

  • 线程池

  • 缓存系统

二、非安全单例模式(存在问题)

2.1 懒汉式(线程不安全)

java 复制代码
public class UnsafeSingleton {
    private static UnsafeSingleton instance;
    
    private UnsafeSingleton() {
        // 防止反射创建
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    public static UnsafeSingleton getInstance() {
        if (instance == null) {  // 多线程可能同时进入
            instance = new UnsafeSingleton();
        }
        return instance;
    }
}

问题:多线程环境下可能创建多个实例

三、线程安全的单例实现方式

3.1 饿汉式(Eager Initialization)

java 复制代码
public class EagerSingleton {
    // 类加载时立即初始化(线程安全)
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    private EagerSingleton() {
        // 防止反射攻击
        if (INSTANCE != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

优点 :简单、线程安全
缺点:类加载时就创建,可能造成资源浪费

3.2 静态代码块饿汉式

java 复制代码
public class StaticBlockSingleton {
    private static final StaticBlockSingleton INSTANCE;
    
    static {
        try {
            INSTANCE = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Error creating singleton instance", e);
        }
    }
    
    private StaticBlockSingleton() {
        // 初始化
    }
    
    public static StaticBlockSingleton getInstance() {
        return INSTANCE;
    }
}

3.3 懒汉式(同步方法)

java 复制代码
public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;
    
    private SynchronizedSingleton() {
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    // 同步方法,线程安全但性能差
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

缺点:每次获取实例都需要同步,性能差

3.4 双重检查锁(Double-Checked Locking)

java 复制代码
public class DoubleCheckedSingleton {
    // 使用volatile防止指令重排
    private static volatile DoubleCheckedSingleton instance;
    
    private DoubleCheckedSingleton() {
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

注意 :必须使用volatile关键字,防止指令重排序问题

3.5 静态内部类(Bill Pugh Singleton)

java 复制代码
public class InnerClassSingleton {
    
    private InnerClassSingleton() {
        // 防止反射攻击
        if (SingletonHolder.INSTANCE != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    // 静态内部类在需要时才加载
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点:延迟加载、线程安全、高效

3.6 枚举(Joshua Bloch推荐)

java 复制代码
public enum EnumSingleton {
    INSTANCE;
    
    private int value;
    
    public void doSomething() {
        System.out.println("Singleton with enum");
    }
    
    public int getValue() {
        return value;
    }
    
    public void setValue(int value) {
        this.value = value;
    }
    
    // 使用方法
    public static void main(String[] args) {
        EnumSingleton.INSTANCE.doSomething();
        EnumSingleton.INSTANCE.setValue(10);
    }
}

优点

  • 防止反射攻击(枚举不能通过反射创建)

  • 防止反序列化创建新对象

  • 线程安全

  • 代码简洁

四、防止破坏单例的方法

4.1 防止反射攻击

java 复制代码
public class AntiReflectionSingleton {
    private static volatile AntiReflectionSingleton instance;
    private static boolean initialized = false;
    
    private AntiReflectionSingleton() {
        synchronized (AntiReflectionSingleton.class) {
            if (initialized) {
                throw new RuntimeException("Cannot reflectively create singleton objects");
            }
            initialized = true;
        }
    }
    
    public static AntiReflectionSingleton getInstance() {
        if (instance == null) {
            synchronized (AntiReflectionSingleton.class) {
                if (instance == null) {
                    instance = new AntiReflectionSingleton();
                }
            }
        }
        return instance;
    }
}

4.2 防止序列化破坏

java 复制代码
import java.io.Serializable;

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();
    
    private SerializableSingleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance");
        }
    }
    
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }
    
    // 关键方法:防止反序列化创建新实例
    protected Object readResolve() {
        return getInstance();
    }
}

4.3 完整的安全单例示例

java 复制代码
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 线程安全、防止反射、防止序列化破坏的单例模式
 */
public class PerfectSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final AtomicReference<PerfectSingleton> INSTANCE = 
        new AtomicReference<>();
    private static volatile boolean initialized = false;
    
    private PerfectSingleton() {
        synchronized (PerfectSingleton.class) {
            if (initialized) {
                throw new RuntimeException("Cannot reflectively create singleton objects");
            }
            initialized = true;
        }
        // 初始化代码
    }
    
    public static PerfectSingleton getInstance() {
        PerfectSingleton current = INSTANCE.get();
        if (current == null) {
            synchronized (PerfectSingleton.class) {
                current = INSTANCE.get();
                if (current == null) {
                    current = new PerfectSingleton();
                    INSTANCE.set(current);
                }
            }
        }
        return current;
    }
    
    protected Object readResolve() {
        return getInstance();
    }
    
    // 防止克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Singleton cannot be cloned");
    }
}

五、现代Java中的单例实现

5.1 使用Java 8+的Supplier接口

java 复制代码
import java.util.function.Supplier;

public class SupplierSingleton {
    private static final Supplier<SupplierSingleton> INSTANCE = 
        SupplierSingleton::createInstance;
    
    private SupplierSingleton() {
        if (Holder.INSTANCE != null) {
            throw new RuntimeException("Use getInstance() method");
        }
    }
    
    private static SupplierSingleton createInstance() {
        return new SupplierSingleton();
    }
    
    private static class Holder {
        static final SupplierSingleton INSTANCE = SupplierSingleton.getInstance();
    }
    
    public static SupplierSingleton getInstance() {
        return INSTANCE.get();
    }
}

5.2 使用CompletableFuture实现延迟加载

java 复制代码
import java.util.concurrent.CompletableFuture;

public class AsyncSingleton {
    private static CompletableFuture<AsyncSingleton> future;
    
    private AsyncSingleton() {
        // 初始化
    }
    
    public static CompletableFuture<AsyncSingleton> getInstanceAsync() {
        if (future == null) {
            synchronized (AsyncSingleton.class) {
                if (future == null) {
                    future = CompletableFuture.supplyAsync(AsyncSingleton::new);
                }
            }
        }
        return future;
    }
    
    // 同步获取
    public static AsyncSingleton getInstance() {
        try {
            return getInstanceAsync().get();
        } catch (Exception e) {
            throw new RuntimeException("Error getting singleton instance", e);
        }
    }
}

六、单例模式的最佳实践

6.1 选择建议

场景 推荐方案 理由
简单应用 饿汉式 简单可靠
需要延迟加载 静态内部类 线程安全,延迟加载
需要防止反射和序列化攻击 枚举 最安全
需要高性能 双重检查锁 性能好,延迟加载

6.2 代码规范示例

java 复制代码
/**
 * 配置管理器单例
 * 使用枚举实现,确保线程安全和防止反射攻击
 */
public enum ConfigManager {
    INSTANCE;
    
    private Properties config;
    
    private ConfigManager() {
        loadConfig();
    }
    
    private void loadConfig() {
        config = new Properties();
        try (InputStream input = getClass().getClassLoader()
                .getResourceAsStream("config.properties")) {
            config.load(input);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }
    
    public String getProperty(String key) {
        return config.getProperty(key);
    }
    
    public String getProperty(String key, String defaultValue) {
        return config.getProperty(key, defaultValue);
    }
}

6.3 测试单例模式

java 复制代码
import java.lang.reflect.Constructor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingletonTest {
    
    // 测试多线程安全性
    public static void testThreadSafety() throws InterruptedException {
        final int threadCount = 100;
        final CountDownLatch latch = new CountDownLatch(threadCount);
        final Set<Object> instances = Collections.synchronizedSet(new HashSet<>());
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            executor.execute(() -> {
                instances.add(DoubleCheckedSingleton.getInstance());
                latch.countDown();
            });
        }
        
        latch.await();
        executor.shutdown();
        
        System.out.println("Unique instances created: " + instances.size());
        // 应该是1
    }
    
    // 测试反射攻击
    public static void testReflectionAttack() {
        try {
            Constructor<DoubleCheckedSingleton> constructor = 
                DoubleCheckedSingleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            DoubleCheckedSingleton instance1 = constructor.newInstance();
            DoubleCheckedSingleton instance2 = constructor.newInstance();
            
            System.out.println("Reflection attack succeeded: " + 
                (instance1 != instance2));
        } catch (Exception e) {
            System.out.println("Reflection attack failed: " + e.getMessage());
        }
    }
}

七、Spring框架中的单例

7.1 Spring单例 vs Java单例

java 复制代码
@Component
@Scope("singleton")  // 默认就是singleton
public class SpringSingleton {
    // Spring容器管理的单例
    // 与Java单例的区别:
    // 1. 作用域不同(应用上下文)
    // 2. 生命周期由Spring管理
    // 3. 默认是单例,但可以配置
}

7.2 线程安全的Spring Bean

java 复制代码
@Service
public class UserService {
    // Service通常是无状态的,天然线程安全
    // 如果需要状态,使用ThreadLocal
    private final ThreadLocal<User> currentUser = new ThreadLocal<>();
    
    public void setCurrentUser(User user) {
        currentUser.set(user);
    }
    
    public User getCurrentUser() {
        return currentUser.get();
    }
}

八、总结

8.1 安全单例的要点

  1. 线程安全:确保多线程环境下只创建一个实例

  2. 延迟加载:避免不必要的资源占用

  3. 防止反射:通过标志位或异常阻止反射创建

  4. 防止序列化 :实现readResolve()方法

  5. 防止克隆 :重写clone()方法并抛出异常

8.2 推荐方案

  1. 简单场景:使用饿汉式

  2. 常规需求:使用静态内部类

  3. 高安全要求:使用枚举

  4. 复杂初始化:使用双重检查锁

8.3 注意事项

  • 单例模式可能隐藏类之间的依赖关系

  • 单例的单元测试可能比较困难

  • 考虑使用依赖注入框架管理单例

  • 避免在单例中保存可变状态

选择适合的单例实现方式需要综合考虑线程安全、性能、序列化需求以及具体的使用场景。

相关推荐
Qiuner2 小时前
Spring Boot 全局异常处理策略设计(一):异常不只是 try-catch
java·spring boot·后端
superman超哥2 小时前
Rust 错误处理模式:Result、?运算符与 anyhow 的最佳实践
开发语言·后端·rust·运算符·anyhow·rust 错误处理
lly2024062 小时前
Web 品质样式表
开发语言
Wang's Blog2 小时前
Nodejs-HardCore: 模块管理与I/O操作详解
开发语言·nodejs
强子感冒了2 小时前
Java List学习笔记:ArrayList与LinkedList的实现源码分析
java·笔记·学习
微爱帮监所写信寄信2 小时前
微爱帮监狱寄信写信小程序PHP底层优化框架
java·开发语言·数据库·spring·微信·php·mybatis
琥珀.2 小时前
查看linux下java服务进程是否正常
java·linux·运维
lly2024062 小时前
R 语言注释指南
开发语言
Coder_Boy_2 小时前
基于SpringAI企业级智能教学考试平台核心模块协同总结与最佳实践方案
java·大数据·人工智能