【设计模式手册005】单例模式 - 唯一实例的优雅实现

设计模式手册005:单例模式 - 唯一实例的优雅实现

本文是「设计模式手册」系列第005篇,我们将深入探讨单例模式,这种模式确保一个类只有一个实例,并提供全局访问点,是设计模式中最简单也最容易用错的一种。

1. 场景:我们为何需要单例模式?

在软件开发中,有些对象我们只需要一个实例,比如:

  • 配置管理器:整个应用只需要一份配置
  • 数据库连接池:避免重复创建连接
  • 日志记录器:统一记录日志
  • 线程池:管理所有线程资源
  • 缓存管理器:全局缓存共享
  • 设备驱动对象:物理设备只能有一个访问点

错误做法的代价

java 复制代码
public class ConfigManager {
    private Map<String, String> configs = new HashMap<>();
    
    public ConfigManager() {
        // 加载配置文件的昂贵操作
        loadConfigFromFile();
    }
    
    private void loadConfigFromFile() {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 读取配置文件...
        configs.put("db.url", "jdbc:mysql://localhost:3306/test");
        configs.put("db.username", "root");
        configs.put("app.name", "MyApplication");
    }
    
    public String getConfig(String key) {
        return configs.get(key);
    }
}

// 问题:每次使用都创建新实例
public class Application {
    public void start() {
        // 每次调用都创建新的ConfigManager,重复加载配置
        String dbUrl = new ConfigManager().getConfig("db.url");
        String appName = new ConfigManager().getConfig("app.name");
        
        // 创建了多个实例,浪费资源,且配置可能不一致
    }
}

这种实现的痛点

  • 资源浪费:重复创建相同对象
  • 性能问题:初始化操作重复执行
  • 状态不一致:多个实例可能状态不同
  • 内存泄漏:无用的对象占用内存

2. 单例模式:定义与本质

2.1 模式定义

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。

2.2 核心要素

java 复制代码
public class Singleton {
    // 1. 私有静态实例
    private static Singleton instance;
    
    // 2. 私有构造函数
    private Singleton() {
        // 防止外部通过new创建实例
    }
    
    // 3. 公共静态访问方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3. 深入理解:单例模式的演进之路

3.1 第一重:饿汉式单例

特点:类加载时就创建实例,线程安全但可能浪费资源

java 复制代码
public class EagerSingleton {
    // 类加载时立即创建实例
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    private EagerSingleton() {
        System.out.println("EagerSingleton实例被创建");
        // 模拟初始化耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
    
    public void showMessage() {
        System.out.println("Hello from EagerSingleton!");
    }
}

// 测试
public class EagerSingletonTest {
    public static void main(String[] args) {
        // 即使没有调用getInstance,实例也会在类加载时创建
        System.out.println("程序启动");
        // 实例已经在类加载时创建
        EagerSingleton.getInstance().showMessage();
    }
}

3.2 第二重:懒汉式单例(线程不安全)

特点:延迟加载,但多线程环境下可能创建多个实例

java 复制代码
public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        System.out.println("LazySingleton实例被创建");
    }
    
    // 线程不安全!
    public static LazySingleton getInstance() {
        if (instance == null) {
            // 多个线程可能同时进入这里
            instance = new LazySingleton();
        }
        return instance;
    }
}

// 演示线程安全问题
public class ThreadUnsafeTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                LazySingleton singleton = LazySingleton.getInstance();
                System.out.println("线程" + Thread.currentThread().getId() + "获取实例: " + singleton);
            });
        }
        
        executor.shutdown();
        // 可能输出多个"LazySingleton实例被创建"
    }
}

3.3 第三重:线程安全的懒汉式

3.3.1 方法同步
java 复制代码
public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;
    
    private SynchronizedSingleton() {
        System.out.println("SynchronizedSingleton实例被创建");
    }
    
    // 线程安全,但性能较差
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}
3.3.2 双重检查锁定(DCL)
java 复制代码
public class DoubleCheckedLockingSingleton {
    // 使用volatile防止指令重排序
    private static volatile DoubleCheckedLockingSingleton instance;
    
    private DoubleCheckedLockingSingleton() {
        System.out.println("DoubleCheckedLockingSingleton实例被创建");
    }
    
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

3.4 第四重:静态内部类实现

推荐用法:兼顾延迟加载和线程安全

java 复制代码
public class StaticInnerClassSingleton {
    
    private StaticInnerClassSingleton() {
        System.out.println("StaticInnerClassSingleton实例被创建");
    }
    
    // 静态内部类在第一次被引用时才会加载
    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }
    
    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.5 第五重:枚举单例

最佳实践:Effective Java作者Joshua Bloch推荐的方式

java 复制代码
public enum EnumSingleton {
    INSTANCE;
    
    // 可以添加方法
    public void doSomething() {
        System.out.println("枚举单例方法执行");
    }
    
    // 可以持有状态
    private String data;
    
    public String getData() {
        return data;
    }
    
    public void setData(String data) {
        this.data = data;
    }
}

// 使用
public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton singleton = EnumSingleton.INSTANCE;
        singleton.setData("Hello World");
        singleton.doSomething();
        System.out.println(singleton.getData());
    }
}

4. 实战案例:完整的配置管理器

java 复制代码
/**
 * 配置管理器单例
 * 使用静态内部类实现,兼顾线程安全和延迟加载
 */
public class ConfigManager {
    private final Properties properties;
    private final String configFile = "application.properties";
    
    // 私有构造函数
    private ConfigManager() {
        properties = new Properties();
        loadConfig();
    }
    
    // 静态内部类
    private static class SingletonHolder {
        private static final ConfigManager INSTANCE = new ConfigManager();
    }
    
    public static ConfigManager getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    private void loadConfig() {
        try (InputStream input = getClass().getClassLoader().getResourceAsStream(configFile)) {
            if (input == null) {
                System.out.println("找不到配置文件: " + configFile);
                return;
            }
            properties.load(input);
            System.out.println("配置文件加载完成");
        } catch (IOException e) {
            System.err.println("加载配置文件失败: " + e.getMessage());
        }
    }
    
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
    
    public String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }
    
    public int getIntProperty(String key, int defaultValue) {
        String value = properties.getProperty(key);
        if (value != null) {
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException e) {
                System.err.println("配置项 " + key + " 不是有效的整数: " + value);
            }
        }
        return defaultValue;
    }
    
    public boolean getBooleanProperty(String key, boolean defaultValue) {
        String value = properties.getProperty(key);
        if (value != null) {
            return Boolean.parseBoolean(value);
        }
        return defaultValue;
    }
    
    // 重新加载配置
    public void reload() {
        properties.clear();
        loadConfig();
    }
    
    // 获取所有配置键
    public Set<String> getAllKeys() {
        return properties.stringPropertyNames();
    }
}

// 使用示例
public class ConfigManagerTest {
    public static void main(String[] args) {
        ConfigManager config = ConfigManager.getInstance();
        
        // 获取配置
        String dbUrl = config.getProperty("db.url", "jdbc:mysql://localhost:3306/default");
        int maxConnections = config.getIntProperty("db.max.connections", 10);
        boolean debugMode = config.getBooleanProperty("app.debug", false);
        
        System.out.println("数据库URL: " + dbUrl);
        System.out.println("最大连接数: " + maxConnections);
        System.out.println("调试模式: " + debugMode);
        
        // 显示所有配置
        System.out.println("所有配置项:");
        config.getAllKeys().forEach(key -> 
            System.out.println(key + " = " + config.getProperty(key))
        );
    }
}

5. Spring框架中的单例模式

Spring容器默认使用单例作用域,但不同于传统的单例模式:

5.1 Spring的单例Bean

java 复制代码
@Component
@Scope("singleton") // 默认就是singleton,可以省略
public class DatabaseConnectionPool {
    private final List<Connection> connections = new ArrayList<>();
    private final int maxSize = 10;
    
    @PostConstruct
    public void init() {
        System.out.println("初始化数据库连接池");
        // 初始化连接
        for (int i = 0; i < maxSize; i++) {
            connections.add(createConnection());
        }
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("关闭数据库连接池");
        connections.forEach(this::closeConnection);
        connections.clear();
    }
    
    public Connection getConnection() {
        // 获取连接逻辑
        return connections.isEmpty() ? createConnection() : connections.remove(0);
    }
    
    public void releaseConnection(Connection connection) {
        if (connections.size() < maxSize) {
            connections.add(connection);
        } else {
            closeConnection(connection);
        }
    }
    
    private Connection createConnection() {
        // 创建数据库连接
        return null; // 实际实现
    }
    
    private void closeConnection(Connection connection) {
        // 关闭连接
    }
}

// 使用
@Service
public class UserService {
    private final DatabaseConnectionPool connectionPool;
    
    @Autowired
    public UserService(DatabaseConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
    }
    
    public void processUserData() {
        Connection connection = connectionPool.getConnection();
        try {
            // 使用连接处理数据
        } finally {
            connectionPool.releaseConnection(connection);
        }
    }
}

5.2 Spring的单例注册表

java 复制代码
@Component
public class SingletonRegistry {
    
    private final Map<String, Object> singletons = new ConcurrentHashMap<>();
    
    public void registerSingleton(String name, Object singleton) {
        if (singletons.containsKey(name)) {
            throw new IllegalStateException("单例 '" + name + "' 已存在");
        }
        singletons.put(name, singleton);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getSingleton(String name, Class<T> requiredType) {
        Object singleton = singletons.get(name);
        if (singleton == null) {
            return null;
        }
        if (!requiredType.isInstance(singleton)) {
            throw new IllegalArgumentException("单例 '" + name + "' 不是 " + requiredType.getName() + " 类型");
        }
        return (T) singleton;
    }
    
    public String[] getSingletonNames() {
        return singletons.keySet().toArray(new String[0]);
    }
    
    public int getSingletonCount() {
        return singletons.size();
    }
}

// 配置类
@Configuration
public class SingletonConfig {
    
    @Bean
    public SingletonRegistry singletonRegistry() {
        return new SingletonRegistry();
    }
    
    @Bean
    public CacheManager cacheManager(SingletonRegistry registry) {
        CacheManager cacheManager = new CacheManager();
        registry.registerSingleton("cacheManager", cacheManager);
        return cacheManager;
    }
}

6. 单例模式的进阶用法

6.1 线程局部单例

java 复制代码
/**
 * 线程局部单例:每个线程有自己的单例实例
 */
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
        ThreadLocal.withInitial(ThreadLocalSingleton::new);
    
    private ThreadLocalSingleton() {
        System.out.println("创建ThreadLocalSingleton实例,线程: " + Thread.currentThread().getName());
    }
    
    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
    
    public void doSomething() {
        System.out.println("执行操作,线程: " + Thread.currentThread().getName());
    }
    
    // 重要:使用完毕后需要清理,防止内存泄漏
    public static void remove() {
        threadLocalInstance.remove();
    }
}

// 测试
public class ThreadLocalSingletonTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        for (int i = 0; i < 6; i++) {
            final int taskId = i;
            executor.execute(() -> {
                ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
                singleton.doSomething();
                
                // 模拟任务执行
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                // 任务完成后清理
                ThreadLocalSingleton.remove();
            });
        }
        
        executor.shutdown();
    }
}

6.2 单例对象池

java 复制代码
/**
 * 单例对象池:管理有限数量的单例对象
 */
public class SingletonObjectPool<T> {
    private final Class<T> objectClass;
    private final BlockingQueue<T> objectPool;
    private final int maxSize;
    private final AtomicInteger createdCount = new AtomicInteger(0);
    
    public SingletonObjectPool(Class<T> objectClass, int maxSize) {
        this.objectClass = objectClass;
        this.maxSize = maxSize;
        this.objectPool = new ArrayBlockingQueue<>(maxSize);
    }
    
    public T borrowObject() throws Exception {
        T object = objectPool.poll();
        if (object != null) {
            return object;
        }
        
        if (createdCount.get() < maxSize) {
            synchronized (this) {
                if (createdCount.get() < maxSize) {
                    object = objectClass.newInstance();
                    createdCount.incrementAndGet();
                    return object;
                }
            }
        }
        
        // 等待其他对象归还
        return objectPool.take();
    }
    
    public void returnObject(T object) {
        if (object != null) {
            objectPool.offer(object);
        }
    }
    
    public int getAvailableCount() {
        return objectPool.size();
    }
    
    public int getCreatedCount() {
        return createdCount.get();
    }
}

// 使用示例
public class DatabaseConnection {
    private final String connectionId;
    
    public DatabaseConnection() {
        this.connectionId = "Connection-" + UUID.randomUUID().toString().substring(0, 8);
        System.out.println("创建数据库连接: " + connectionId);
    }
    
    public void executeQuery(String sql) {
        System.out.println(connectionId + " 执行查询: " + sql);
    }
    
    public String getConnectionId() {
        return connectionId;
    }
}

// 测试对象池
public class ObjectPoolTest {
    public static void main(String[] args) throws Exception {
        SingletonObjectPool<DatabaseConnection> pool = 
            new SingletonObjectPool<>(DatabaseConnection.class, 3);
        
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    DatabaseConnection connection = pool.borrowObject();
                    System.out.println("任务 " + taskId + " 获取连接: " + connection.getConnectionId());
                    
                    // 模拟数据库操作
                    connection.executeQuery("SELECT * FROM users WHERE id = " + taskId);
                    Thread.sleep(500);
                    
                    // 归还连接
                    pool.returnObject(connection);
                    System.out.println("任务 " + taskId + " 归还连接: " + connection.getConnectionId());
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
        
        System.out.println("最终可用连接数: " + pool.getAvailableCount());
        System.out.println("总共创建连接数: " + pool.getCreatedCount());
    }
}

7. 单例模式的陷阱与解决方案

7.1 反射攻击

java 复制代码
public class ReflectionSafeSingleton {
    private static volatile ReflectionSafeSingleton instance;
    private static boolean initialized = false;
    
    private ReflectionSafeSingleton() {
        // 防止反射攻击
        if (initialized) {
            throw new RuntimeException("单例实例已存在,不能通过反射创建");
        }
        initialized = true;
        System.out.println("ReflectionSafeSingleton实例被创建");
    }
    
    public static ReflectionSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ReflectionSafeSingleton.class) {
                if (instance == null) {
                    instance = new ReflectionSafeSingleton();
                }
            }
        }
        return instance;
    }
}

// 反射攻击测试
public class ReflectionAttackTest {
    public static void main(String[] args) throws Exception {
        // 正常获取实例
        ReflectionSafeSingleton instance1 = ReflectionSafeSingleton.getInstance();
        
        // 尝试通过反射创建新实例
        try {
            Constructor<ReflectionSafeSingleton> constructor = 
                ReflectionSafeSingleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            ReflectionSafeSingleton instance2 = constructor.newInstance();
            System.out.println("反射攻击成功");
        } catch (Exception e) {
            System.out.println("反射攻击被阻止: " + e.getMessage());
        }
    }
}

7.2 序列化攻击

java 复制代码
public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();
    
    private SerializableSingleton() {
        System.out.println("SerializableSingleton实例被创建");
    }
    
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }
    
    // 防止序列化破坏单例
    protected Object readResolve() {
        return INSTANCE;
    }
}

// 序列化攻击测试
public class SerializationAttackTest {
    public static void main(String[] args) throws Exception {
        SerializableSingleton instance1 = SerializableSingleton.getInstance();
        
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance1);
        oos.close();
        
        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        SerializableSingleton instance2 = (SerializableSingleton) ois.readObject();
        ois.close();
        
        System.out.println("实例1: " + instance1);
        System.out.println("实例2: " + instance2);
        System.out.println("是否是同一个实例: " + (instance1 == instance2));
    }
}

8. 单例模式 vs 其他模式

8.1 单例模式 vs 静态类

  • 单例模式:可以继承、实现接口、延迟加载、支持状态
  • 静态类:只有静态方法,不能继承,类加载时就初始化

8.2 单例模式 vs 工厂模式

  • 单例模式:确保只有一个实例
  • 工厂模式:负责创建对象,不限制实例数量

8.3 单例模式 vs 享元模式

  • 单例模式:一个类只有一个实例
  • 享元模式:一个类有多个实例,但共享相同的内在状态

9. 总结与思考

9.1 单例模式的优点

  1. 严格控制实例数量:确保全局只有一个实例
  2. 全局访问点:方便其他对象访问
  3. 节省资源:避免重复创建相同对象
  4. 状态共享:所有使用者共享相同状态

9.2 单例模式的缺点

  1. 违反单一职责原则:同时负责创建和管理实例
  2. 难以测试:全局状态使得单元测试困难
  3. 隐藏依赖关系:使用单例的类依赖关系不明确
  4. 可能成为上帝对象:过度使用导致单例类过于庞大

9.3 设计思考

单例模式的本质是**"受控的全局状态"**。它通过限制实例化来确保状态的一致性,但这也带来了测试和维护的挑战。

深入思考的角度

"单例模式看似简单,实则暗藏玄机。它需要在简洁性和灵活性之间找到平衡,在确保唯一性的同时,还要考虑线程安全、序列化、反射攻击等各种边界情况。"

在实际应用中,单例模式有很多需要注意的地方:

  • Spring的Singleton作用域与经典单例的区别
  • 单例在分布式环境下的挑战
  • 单例与依赖注入的配合使用
  • 如何避免单例成为瓶颈

最佳实践建议

  1. 优先使用枚举或静态内部类实现单例
  2. 考虑使用依赖注入框架管理单例
  3. 避免在单例中保存可变状态
  4. 为单例编写适当的销毁方法
  5. 在多线程环境下充分测试

使用场景判断

  • 适合:线程池、缓存、配置、日志、设备访问
  • 不适合:需要测试的业务逻辑、可能变化的状态管理、需要多态的场景

下一篇预告:设计模式手册006 - 建造者模式:如何优雅地构建复杂对象?


版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

相关推荐
二川bro2 小时前
第48节:WebAssembly加速与C++物理引擎编译
java·c++·wasm
⑩-2 小时前
苍穹外卖Day(8)(9)
java·spring boot·mybatis
IUGEI2 小时前
Websocket、HTTP/2、HTTP/3原理解析
java·网络·后端·websocket·网络协议·http·https
程序猿_极客2 小时前
【2025 最新】 Maven 下载安装与配置教程(超详细带图文Windows 版):从入门到实战
java·开发语言·windows·maven·maven安装
朴shu2 小时前
揭秘高性能协同白板:轻松实现多人实时协作(一)
前端·设计模式·架构
q***01652 小时前
【保姆级教程】apache-tomcat的安装配置教程
java·tomcat·apache
得物技术3 小时前
Golang HTTP请求超时与重试:构建高可靠网络请求|得物技术
java·后端·go
合作小小程序员小小店3 小时前
web网页开发,在线短视频管理系统,基于Idea,html,css,jQuery,java,springboot,mysql。
java·前端·spring boot·mysql·vue·intellij-idea
培风图楠3 小时前
Java个人学习笔记
java·笔记·学习