单例模式(Singleton Pattern)

76 阅读9分钟

单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来获取该实例。

使用场景

  1. 一般情况下:推荐使用静态内部类实现,平衡了各种需求
  2. 对安全性要求极高:使用枚举实现
  3. 需要传递初始化参数:使用双重检查锁定
  4. 资源占用小且启动时就需要:使用饿汉式
  5. 简单场景且对性能要求不高:使用基本懒汉式

SpringBoot项目中Demo

单例模式 - 配置管理器(枚举实现方式)

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 单例模式 - 配置管理器(枚举实现方式)
 * 
 * 符合阿里巴巴规范的单例实现:
 * 1. 使用枚举实现单例,这是《Effective Java》推荐的实现方式
 * 2. 自动处理序列化/反序列化问题
 * 3. 线程安全,由JVM保证
 * 4. 防止反射攻击
 * 
 * @author demo
 * @since 1.0.0
 */
public enum ConfigManagerSingleton {

    /**
     * 单例实例
     */
    INSTANCE;

    /**
     * 使用ConcurrentHashMap保证线程安全
     */
    private final Map<String, String> configMap;

    /**
     * 枚举的构造方法在类加载时自动调用
     */
    ConfigManagerSingleton() {
        this.configMap = new ConcurrentHashMap<>();
        initDefaultConfig();
    }

    /**
     * 初始化默认配置
     */
    private void initDefaultConfig() {
        configMap.put("app.name", "demo-application");
        configMap.put("app.version", "1.0.0");
        configMap.put("app.timezone", "Asia/Shanghai");
    }

    /**
     * 获取配置值
     * 
     * @param key 配置键
     * @return 配置值,如果不存在则返回null
     */
    public String getConfig(String key) {
        return configMap.get(key);
    }

    /**
     * 获取配置值,提供默认值
     * 
     * @param key 配置键
     * @param defaultValue 默认值
     * @return 配置值,如果不存在则返回默认值
     */
    public String getConfig(String key, String defaultValue) {
        return configMap.getOrDefault(key, defaultValue);
    }

    /**
     * 设置配置值
     * 
     * @param key 配置键
     * @param value 配置值
     */
    public void setConfig(String key, String value) {
        if (key == null || key.trim().isEmpty()) {
            throw new IllegalArgumentException("配置键不能为空");
        }
        configMap.put(key, value);
    }

    /**
     * 移除配置
     * 
     * @param key 配置键
     * @return 被移除的值,如果不存在则返回null
     */
    public String removeConfig(String key) {
        return configMap.remove(key);
    }

    /**
     * 获取所有配置
     * 
     * @return 配置的副本,防止外部修改内部数据
     */
    public Map<String, String> getAllConfigs() {
        return new ConcurrentHashMap<>(configMap);
    }

    /**
     * 清空所有配置(仅用于测试场景)
     */
    public void clearAll() {
        configMap.clear();
    }

    /**
     * 获取配置数量
     * 
     * @return 配置数量
     */
    public int size() {
        return configMap.size();
    }
}

单例模式 - 配置管理器(枚举实现方式)单元测试

import org.junit.jupiter.api.Test;

import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 配置管理器单例测试类
 * 
 * 测试枚举实现的单例模式的正确性、线程安全性以及功能完整性
 * 
 * @author demo
 * @since 1.0.0
 */
class ConfigManagerSingletonTest {

    /**
     * 测试单例实例的唯一性
     */
    @Test
    void testSingletonInstanceUniqueness() {
        ConfigManagerSingleton instance1 = ConfigManagerSingleton.INSTANCE;
        ConfigManagerSingleton instance2 = ConfigManagerSingleton.INSTANCE;
        
        assertSame(instance1, instance2, "枚举单例实例应该是唯一的");
        assertEquals(instance1.hashCode(), instance2.hashCode(), "相同实例的哈希码应该相同");
    }

    /**
     * 测试获取配置
     */
    @Test
    void testGetConfig() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        String appName = configManager.getConfig("app.name");
        assertNotNull(appName, "app.name配置应该存在");
        assertEquals("demo-application", appName, "app.name的值应该是demo-application");
        
        String notExistKey = configManager.getConfig("not.exist.key");
        assertNull(notExistKey, "不存在的配置键应该返回null");
    }

    /**
     * 测试获取配置带默认值
     */
    @Test
    void testGetConfigWithDefaultValue() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        String defaultValue = configManager.getConfig("not.exist.key", "default-value");
        assertEquals("default-value", defaultValue, "不存在的配置键应该返回默认值");
        
        String existValue = configManager.getConfig("app.name", "default-value");
        assertEquals("demo-application", existValue, "存在的配置键应该返回实际值");
    }

    /**
     * 测试设置配置
     */
    @Test
    void testSetConfig() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        configManager.setConfig("test.key", "test-value");
        String value = configManager.getConfig("test.key");
        assertEquals("test-value", value, "设置的配置值应该正确");
        
        // 清理测试数据
        configManager.removeConfig("test.key");
    }

    /**
     * 测试设置空键的异常
     */
    @Test
    void testSetConfigWithEmptyKey() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        assertThrows(IllegalArgumentException.class, () -> {
            configManager.setConfig(null, "value");
        }, "设置null键应该抛出异常");
        
        assertThrows(IllegalArgumentException.class, () -> {
            configManager.setConfig("", "value");
        }, "设置空字符串键应该抛出异常");
        
        assertThrows(IllegalArgumentException.class, () -> {
            configManager.setConfig("  ", "value");
        }, "设置空白键应该抛出异常");
    }

    /**
     * 测试移除配置
     */
    @Test
    void testRemoveConfig() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        configManager.setConfig("test.remove.key", "test-value");
        String removedValue = configManager.removeConfig("test.remove.key");
        assertEquals("test-value", removedValue, "移除的值应该正确");
        
        String afterRemove = configManager.getConfig("test.remove.key");
        assertNull(afterRemove, "移除后应该返回null");
        
        String removeNotExist = configManager.removeConfig("not.exist.key");
        assertNull(removeNotExist, "移除不存在的键应该返回null");
    }

    /**
     * 测试获取所有配置
     */
    @Test
    void testGetAllConfigs() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        Map<String, String> configs = configManager.getAllConfigs();
        
        assertNotNull(configs, "配置Map不应该为null");
        assertTrue(configs.containsKey("app.name"), "应该包含app.name配置");
        assertTrue(configs.containsKey("app.version"), "应该包含app.version配置");
        
        // 验证返回的是副本
        configs.put("test.key", "test-value");
        assertFalse(configManager.getAllConfigs().containsKey("test.key"), 
            "修改返回的Map不应该影响原始配置");
    }

    /**
     * 测试配置数量
     */
    @Test
    void testSize() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        int originalSize = configManager.size();
        assertTrue(originalSize > 0, "默认配置数量应该大于0");
        
        configManager.setConfig("test.size.key", "test-value");
        assertEquals(originalSize + 1, configManager.size(), "添加配置后数量应该增加");
        
        configManager.removeConfig("test.size.key");
        assertEquals(originalSize, configManager.size(), "移除配置后数量应该恢复");
    }

    /**
     * 测试清空所有配置
     */
    @Test
    void testClearAll() {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        
        int originalSize = configManager.size();
        configManager.setConfig("test.clear.key", "test-value");
        
        configManager.clearAll();
        assertEquals(0, configManager.size(), "清空后配置数量应该是0");
        
        // 恢复默认配置
        configManager.setConfig("app.name", "demo-application");
        configManager.setConfig("app.version", "1.0.0");
        configManager.setConfig("app.timezone", "Asia/Shanghai");
    }

    /**
     * 测试线程安全性 - 多线程并发读写配置
     */
    @Test
    void testThreadSafety() throws InterruptedException {
        ConfigManagerSingleton configManager = ConfigManagerSingleton.INSTANCE;
        configManager.clearAll();
        
        int threadCount = 50;
        int operationsPerThread = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(threadCount);
        
        AtomicInteger successCount = new AtomicInteger(0);
        
        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            executorService.submit(() -> {
                try {
                    startLatch.await();
                    
                    for (int j = 0; j < operationsPerThread; j++) {
                        String key = "thread-" + threadId + "-key-" + j;
                        String value = "value-" + j;
                        
                        configManager.setConfig(key, value);
                        String retrieved = configManager.getConfig(key);
                        
                        if (value.equals(retrieved)) {
                            successCount.incrementAndGet();
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    fail("线程被中断");
                } finally {
                    endLatch.countDown();
                }
            });
        }
        
        startLatch.countDown();
        endLatch.await();
        executorService.shutdown();
        
        assertEquals(threadCount * operationsPerThread, successCount.get(), 
            "所有读写操作都应该成功");
        
        // 清理测试数据
        configManager.clearAll();
        configManager.setConfig("app.name", "demo-application");
        configManager.setConfig("app.version", "1.0.0");
        configManager.setConfig("app.timezone", "Asia/Shanghai");
    }
}

单例模式 - ID生成器


import java.util.concurrent.atomic.AtomicLong;

/**
 * 单例模式 - ID生成器
 * 
 * 符合阿里巴巴规范的生产级单例实现:
 * 1. 使用双重检查锁定(Double-Checked Locking)实现懒加载
 * 2. 使用volatile关键字防止指令重排序
 * 3. 使用AtomicLong保证线程安全
 * 4. 提供清晰的文档注释
 * 
 * @author demo
 * @since 1.0.0
 */
public final class IdGeneratorSingleton {

    /**
     * 使用volatile关键字防止指令重排序,保证多线程环境下的可见性
     * 符合阿里巴巴规范:必须使用volatile修饰单例实例
     */
    private static volatile IdGeneratorSingleton instance;

    /**
     * 使用AtomicLong保证原子性操作,避免使用synchronized带来的性能问题
     */
    private final AtomicLong counter = new AtomicLong(0);

    /**
     * 私有构造方法,防止外部实例化
     * 符合阿里巴巴规范:单例类必须使用private构造方法
     */
    private IdGeneratorSingleton() {
        // 初始化逻辑
        if (instance != null) {
            throw new IllegalStateException("单例类不允许通过反射创建实例");
        }
    }

    /**
     * 获取单例实例
     * 
     * 使用双重检查锁定(Double-Checked Locking):
     * 1. 第一次检查:避免不必要的同步
     * 2. 同步块:保证线程安全
     * 3. 第二次检查:防止多个线程同时通过第一次检查
     * 
     * @return 单例实例
     */
    public static IdGeneratorSingleton getInstance() {
        if (instance == null) {
            synchronized (IdGeneratorSingleton.class) {
                if (instance == null) {
                    instance = new IdGeneratorSingleton();
                }
            }
        }
        return instance;
    }

    /**
     * 生成下一个ID
     * 
     * @return 下一个ID值
     */
    public long nextId() {
        return counter.incrementAndGet();
    }

    /**
     * 获取当前ID计数器值
     * 
     * @return 当前计数器值
     */
    public long getCurrentValue() {
        return counter.get();
    }

    /**
     * 重置计数器(仅用于测试场景)
     * 
     * @param newValue 重置后的值
     */
    public void reset(long newValue) {
        counter.set(newValue);
    }

    /**
     * 防止通过反序列化创建新实例
     * 
     * @return 已存在的单例实例
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("单例类不允许被克隆");
    }
}

单例模式 - ID生成器 单元测试


import org.junit.jupiter.api.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.*;

/**
 * ID生成器单例测试类
 * 
 * 测试单例模式的线程安全性、懒加载特性以及功能正确性
 * 
 * @author demo
 * @since 1.0.0
 */
class IdGeneratorSingletonTest {

    /**
     * 测试单例实例的唯一性
     */
    @Test
    void testSingletonInstanceUniqueness() {
        IdGeneratorSingleton instance1 = IdGeneratorSingleton.getInstance();
        IdGeneratorSingleton instance2 = IdGeneratorSingleton.getInstance();
        
        assertSame(instance1, instance2, "多次获取的实例应该是同一个对象");
        assertEquals(instance1.hashCode(), instance2.hashCode(), "相同实例的哈希码应该相同");
    }

    /**
     * 测试ID生成功能
     */
    @Test
    void testIdGeneration() {
        IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
        idGenerator.reset(0);
        
        long id1 = idGenerator.nextId();
        long id2 = idGenerator.nextId();
        long id3 = idGenerator.nextId();
        
        assertEquals(1, id1, "第一个ID应该是1");
        assertEquals(2, id2, "第二个ID应该是2");
        assertEquals(3, id3, "第三个ID应该是3");
    }

    /**
     * 测试重置功能
     */
    @Test
    void testReset() {
        IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
        idGenerator.reset(100);
        
        long id1 = idGenerator.nextId();
        assertEquals(101, id1, "重置后的第一个ID应该是101");
        
        idGenerator.reset(0);
        assertEquals(0, idGenerator.getCurrentValue(), "重置后当前值应该是0");
    }

    /**
     * 测试线程安全性 - 多线程并发获取ID
     * 
     * 测试在多线程环境下,ID生成器是否能正确工作且不产生重复ID
     */
    @Test
    void testThreadSafety() throws InterruptedException {
        IdGeneratorSingleton idGenerator = IdGeneratorSingleton.getInstance();
        idGenerator.reset(0);
        
        int threadCount = 100;
        int idsPerThread = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(threadCount);
        
        AtomicInteger[] results = new AtomicInteger[threadCount * idsPerThread];
        AtomicInteger index = new AtomicInteger(0);
        
        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                try {
                    startLatch.await(); // 等待所有线程准备就绪
                    
                    for (int j = 0; j < idsPerThread; j++) {
                        long id = idGenerator.nextId();
                        results[index.getAndIncrement()] = new AtomicInteger((int) id);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    fail("线程被中断");
                } finally {
                    endLatch.countDown();
                }
            });
        }
        
        startLatch.countDown(); // 开始测试
        endLatch.await(); // 等待所有线程完成
        executorService.shutdown();
        
        // 验证生成的ID数量
        assertEquals(threadCount * idsPerThread, idGenerator.getCurrentValue(), 
            "生成的ID总数应该等于线程数 * 每个线程生成的ID数");
        
        // 验证ID没有重复
        boolean[] seen = new boolean[threadCount * idsPerThread + 1];
        for (int i = 0; i < results.length; i++) {
            int id = results[i].get();
            assertTrue(id >= 1 && id <= threadCount * idsPerThread, 
                "ID应该在有效范围内: " + id);
            assertFalse(seen[id], "ID不应该重复: " + id);
            seen[id] = true;
        }
    }

    /**
     * 测试多线程环境下单例实例的唯一性
     */
    @Test
    void testSingletonInstanceInMultiThread() throws InterruptedException {
        int threadCount = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(threadCount);
        
        IdGeneratorSingleton[] instances = new IdGeneratorSingleton[threadCount];
        
        for (int i = 0; i < threadCount; i++) {
            final int index = i;
            executorService.submit(() -> {
                try {
                    startLatch.await();
                    instances[index] = IdGeneratorSingleton.getInstance();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    fail("线程被中断");
                } finally {
                    endLatch.countDown();
                }
            });
        }
        
        startLatch.countDown();
        endLatch.await();
        executorService.shutdown();
        
        // 验证所有线程获取的实例都是同一个
        IdGeneratorSingleton firstInstance = instances[0];
        for (int i = 1; i < threadCount; i++) {
            assertSame(firstInstance, instances[i], 
                "线程 " + i + " 获取的实例应该与第一个线程相同");
        }
    }

    /**
     * 测试克隆保护
     */
    @Test
    void testCloneNotSupportedException() {
        IdGeneratorSingleton instance = IdGeneratorSingleton.getInstance();
        
        assertThrows(CloneNotSupportedException.class, () -> {
            instance.clone();
        }, "单例类不应该支持克隆");
    }
}

单例模式分为两类

饿汉式(Eager Initialization)

import lombok.Getter;

/**
 * 饿汉式(Eager Initialization)
 * 在类加载时就创建实例
 * 线程安全,但可能造成资源浪费
 */
public class Singleton {

    @Getter
    private static Singleton instance = new Singleton();

    private Singleton() {

    }
}

懒汉式(Lazy Initialization)

非线程安全

/**

 * 懒汉式(Lazy Initialization)
 * 在第一次使用时才创建实例
 * 节省资源,但需要注意线程安全问题
 */
public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

线程安全版本(同步方法)

/**
 * 懒汉式(Lazy Initialization)
 */
public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

双重检查锁定

/**
 * 懒汉式(Lazy Initialization)
 * 在第一次使用时才创建实例
 * <p>
 * 既保证线程安全,又提高性能
 * 只在实例为空时才进行同步检查
 */
public class Singleton {
    // 1. 私有静态变量,用volatile修饰:禁止指令重排序,确保实例可见性
    private static volatile Singleton instance;

    // 2. 私有构造器:禁止外部直接创建实例
    private Singleton() {
    }

    // 3. 公有静态获取实例方法:双重检查锁定
    public static Singleton getInstance() {
        // 第一次检查:未加锁,快速判断实例是否已存在(避免频繁进入同步块)
        if (instance == null) {
            // 加锁:确保同一时间只有一个线程进入实例创建逻辑
            synchronized (Singleton.class) {
                // 第二次检查:在锁内再次判断(防止多个线程等待锁时,重复创建实例)
                if (instance == null) {
                    // 创建实例(volatile确保此操作不会被指令重排序)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

静态内部类

/**
 * 懒汉式(Lazy Initialization)
 * 在第一次使用时才创建实例
 * <p>
 * 利用类加载机制保证线程安全
 * 实现延迟加载,性能优秀
 */
public class Singleton {
    // 私有构造方法,防止外部实例化
    private Singleton() {
        // 可以添加防止反射破坏单例的逻辑
        if (SingletonHolder.INSTANCE != null) {
            throw new IllegalStateException("单例实例已存在,不能重复创建");
        }
    }
    
    // 静态内部类,用于持有单例实例
    private static class SingletonHolder {
        // 静态常量,在类加载时初始化
        private static final Singleton INSTANCE = new Singleton();
    }
    
    // 提供公共访问方法
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举实现

/**
 * 懒汉式(Lazy Initialization)
 * 在第一次使用时才创建实例
 * <p>
 * 最安全的实现方式
 * 天然防止反射和序列化攻击
 */
public enum Singleton {
    // 枚举实例,这就是单例的唯一实例
    INSTANCE;
    
    // 可以在这里添加单例类的属性和方法
    private String data;
    
    public String getData() {
        return data;
    }
    
    public void setData(String data) {
        this.data = data;
    }
    
    // 其他业务方法
    public void doSomething() {
        System.out.println("单例对象执行操作");
    }
}