设计模式之:单例模式

文章目录

    • 什么是单例模式?
    • 核心思想
    • 模式结构
    • 单例模式的多种实现方式
      • [1. 饿汉式单例(Eager Initialization)](#1. 饿汉式单例(Eager Initialization))
      • [2. 懒汉式单例(Lazy Initialization)](#2. 懒汉式单例(Lazy Initialization))
      • [3. 双重检查锁单例(Double-Checked Locking)](#3. 双重检查锁单例(Double-Checked Locking))
      • [4. 静态内部类单例(Holder Pattern)](#4. 静态内部类单例(Holder Pattern))
      • [5. 枚举单例(Enum Singleton)](#5. 枚举单例(Enum Singleton))
    • 完整示例:配置管理器单例
      • [1. 配置管理器单例类](#1. 配置管理器单例类)
      • [2. 数据库连接池单例](#2. 数据库连接池单例)
      • [3. 客户端使用示例](#3. 客户端使用示例)
    • 单例模式的优点
      • [1. 严格控制实例数量](#1. 严格控制实例数量)
      • [2. 全局访问点](#2. 全局访问点)
      • [3. 延迟初始化](#3. 延迟初始化)
    • 单例模式的缺点
      • [1. 违反单一职责原则](#1. 违反单一职责原则)
      • [2. 难以测试](#2. 难以测试)
    • 适用场景
    • 最佳实践
      • [1. 优先使用枚举或静态内部类实现](#1. 优先使用枚举或静态内部类实现)
      • [2. 考虑线程安全](#2. 考虑线程安全)
      • [3. 防止反射和反序列化破坏](#3. 防止反射和反序列化破坏)
    • 总结

什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式是设计模式中最简单但也是最容易误用的模式之一。

核心思想

单例模式的核心思想是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 它通过控制对象的创建过程,确保在整个应用程序中只存在一个实例。

模式结构

单例模式包含一个核心角色:

  1. 单例类(Singleton):负责创建自己的唯一实例,并提供访问该实例的全局方法

单例模式的多种实现方式

1. 饿汉式单例(Eager Initialization)

java 复制代码
/**
 * 饿汉式单例 - 线程安全
 * 在类加载时就完成实例化,避免了多线程同步问题
 */
public class EagerSingleton {
    // 在类加载时立即实例化
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    // 私有构造函数,防止外部实例化
    private EagerSingleton() {
        // 防止通过反射创建实例
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象已被创建,禁止通过反射实例化");
        }
        System.out.println("饿汉式单例实例化完成");
    }
    
    // 全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
    
    // 业务方法
    public void showMessage() {
        System.out.println("Hello from EagerSingleton!");
    }
    
    // 防止反序列化破坏单例
    private Object readResolve() {
        return INSTANCE;
    }
}

2. 懒汉式单例(Lazy Initialization)

java 复制代码
/**
 * 懒汉式单例 - 基础版本(线程不安全)
 * 在第一次调用getInstance时才创建实例
 */
public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        System.out.println("懒汉式单例实例化完成");
    }
    
    // 线程不安全的实现
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    
    public void showMessage() {
        System.out.println("Hello from LazySingleton!");
    }
}

/**
 * 懒汉式单例 - 线程安全版本(同步方法)
 * 通过synchronized保证线程安全,但性能较差
 */
public class ThreadSafeLazySingleton {
    private static ThreadSafeLazySingleton instance;
    
    private ThreadSafeLazySingleton() {
        System.out.println("线程安全懒汉式单例实例化完成");
    }
    
    // 使用synchronized保证线程安全,但每次访问都会同步,性能低
    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
    }
    
    public void showMessage() {
        System.out.println("Hello from ThreadSafeLazySingleton!");
    }
}

3. 双重检查锁单例(Double-Checked Locking)

java 复制代码
/**
 * 双重检查锁单例 - 高性能线程安全版本
 * 既保证了线程安全,又提高了性能
 */
public class DoubleCheckedLockingSingleton {
    // 使用volatile禁止指令重排序,保证可见性
    private static volatile DoubleCheckedLockingSingleton instance;
    
    private DoubleCheckedLockingSingleton() {
        System.out.println("双重检查锁单例实例化完成");
    }
    
    public static DoubleCheckedLockingSingleton getInstance() {
        // 第一次检查,避免不必要的同步
        if (instance == null) {
            // 同步代码块
            synchronized (DoubleCheckedLockingSingleton.class) {
                // 第二次检查,确保只有一个线程创建实例
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
    
    public void showMessage() {
        System.out.println("Hello from DoubleCheckedLockingSingleton!");
    }
}

4. 静态内部类单例(Holder Pattern)

java 复制代码
/**
 * 静态内部类单例 - 推荐使用的实现方式
 * 结合了饿汉式的线程安全和懒汉式的延迟加载
 */
public class InnerClassSingleton {
    
    private InnerClassSingleton() {
        System.out.println("静态内部类单例实例化完成");
    }
    
    // 静态内部类,在第一次被引用时才会加载
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    public void showMessage() {
        System.out.println("Hello from InnerClassSingleton!");
    }
    
    // 防止反序列化破坏单例
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

5. 枚举单例(Enum Singleton)

java 复制代码
/**
 * 枚举单例 - Joshua Bloch推荐的方式
 *  Effective Java作者推荐的单例实现方式,绝对防止多实例
 */
public enum EnumSingleton {
    INSTANCE;
    
    // 枚举的构造函数默认就是private的
    EnumSingleton() {
        System.out.println("枚举单例实例化完成");
    }
    
    // 业务方法
    public void showMessage() {
        System.out.println("Hello from EnumSingleton!");
    }
    
    // 可以添加其他业务方法
    public void doBusinessLogic() {
        System.out.println("执行业务逻辑...");
    }
}

完整示例:配置管理器单例

让我们通过一个完整的配置管理器示例来展示单例模式的实际应用。

1. 配置管理器单例类

java 复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 配置管理器单例 - 实际应用示例
 * 在整个应用中只需要一个配置管理器实例
 */
public class ConfigurationManager {
    // 使用静态内部类实现单例
    private static class Holder {
        private static final ConfigurationManager INSTANCE = new ConfigurationManager();
    }
    
    private final Map<String, String> configMap;
    private final Properties properties;
    private boolean initialized = false;
    
    private ConfigurationManager() {
        this.configMap = new HashMap<>();
        this.properties = new Properties();
        System.out.println("配置管理器单例创建完成");
        loadDefaultConfig();
    }
    
    public static ConfigurationManager getInstance() {
        return Holder.INSTANCE;
    }
    
    /**
     * 加载默认配置
     */
    private void loadDefaultConfig() {
        // 模拟加载默认配置
        configMap.put("app.name", "MyApplication");
        configMap.put("app.version", "1.0.0");
        configMap.put("database.url", "jdbc:mysql://localhost:3306/mydb");
        configMap.put("database.username", "admin");
        configMap.put("server.port", "8080");
        configMap.put("log.level", "INFO");
        
        initialized = true;
        System.out.println("默认配置加载完成");
    }
    
    /**
     * 获取配置值
     */
    public String getConfig(String key) {
        if (!initialized) {
            throw new IllegalStateException("配置管理器未初始化");
        }
        return configMap.get(key);
    }
    
    /**
     * 获取配置值,带默认值
     */
    public String getConfig(String key, String defaultValue) {
        String value = getConfig(key);
        return value != null ? value : defaultValue;
    }
    
    /**
     * 设置配置值
     */
    public void setConfig(String key, String value) {
        if (!initialized) {
            throw new IllegalStateException("配置管理器未初始化");
        }
        configMap.put(key, value);
        System.out.println("配置已更新: " + key + " = " + value);
    }
    
    /**
     * 检查配置是否存在
     */
    public boolean containsConfig(String key) {
        return configMap.containsKey(key);
    }
    
    /**
     * 获取所有配置
     */
    public Map<String, String> getAllConfigs() {
        return new HashMap<>(configMap); // 返回副本保护内部数据
    }
    
    /**
     * 重新加载配置
     */
    public void reloadConfig() {
        configMap.clear();
        loadDefaultConfig();
        System.out.println("配置重新加载完成");
    }
    
    /**
     * 打印所有配置
     */
    public void printAllConfigs() {
        System.out.println("\n=== 当前所有配置 ===");
        configMap.forEach((key, value) -> 
            System.out.printf("%-20s: %s%n", key, value));
        System.out.println("===================\n");
    }
}

2. 数据库连接池单例

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 数据库连接池单例 - 另一个实际应用示例
 */
public class DatabaseConnectionPool {
    private static volatile DatabaseConnectionPool instance;
    
    private final List<Connection> connectionPool;
    private final List<Connection> usedConnections;
    private static final int INITIAL_POOL_SIZE = 10;
    private static final int MAX_POOL_SIZE = 20;
    
    private final String url;
    private final String user;
    private final String password;
    
    private DatabaseConnectionPool() {
        this.connectionPool = new ArrayList<>();
        this.usedConnections = new ArrayList<>();
        
        // 从配置管理器获取数据库配置
        ConfigurationManager config = ConfigurationManager.getInstance();
        this.url = config.getConfig("database.url");
        this.user = config.getConfig("database.username");
        this.password = config.getConfig("database.password", "defaultpass");
        
        initializePool();
        System.out.println("数据库连接池初始化完成,初始连接数: " + INITIAL_POOL_SIZE);
    }
    
    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnectionPool.class) {
                if (instance == null) {
                    instance = new DatabaseConnectionPool();
                }
            }
        }
        return instance;
    }
    
    private void initializePool() {
        for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
            try {
                Connection connection = createConnection();
                connectionPool.add(connection);
            } catch (SQLException e) {
                System.err.println("创建数据库连接失败: " + e.getMessage());
            }
        }
    }
    
    private Connection createConnection() throws SQLException {
        // 模拟创建数据库连接
        System.out.println("创建新的数据库连接");
        return DriverManager.getConnection(url, user, password);
    }
    
    public synchronized Connection getConnection() {
        if (connectionPool.isEmpty()) {
            if (usedConnections.size() < MAX_POOL_SIZE) {
                try {
                    Connection connection = createConnection();
                    usedConnections.add(connection);
                    return connection;
                } catch (SQLException e) {
                    throw new RuntimeException("无法创建新连接", e);
                }
            } else {
                throw new RuntimeException("连接池已满,无法获取连接");
            }
        }
        
        Connection connection = connectionPool.remove(connectionPool.size() - 1);
        usedConnections.add(connection);
        return connection;
    }
    
    public synchronized boolean releaseConnection(Connection connection) {
        connectionPool.add(connection);
        return usedConnections.remove(connection);
    }
    
    public synchronized void shutdown() {
        for (Connection connection : connectionPool) {
            try {
                connection.close();
            } catch (SQLException e) {
                System.err.println("关闭连接失败: " + e.getMessage());
            }
        }
        for (Connection connection : usedConnections) {
            try {
                connection.close();
            } catch (SQLException e) {
                System.err.println("关闭连接失败: " + e.getMessage());
            }
        }
        connectionPool.clear();
        usedConnections.clear();
        System.out.println("数据库连接池已关闭");
    }
    
    public int getAvailableConnectionsCount() {
        return connectionPool.size();
    }
    
    public int getUsedConnectionsCount() {
        return usedConnections.size();
    }
}

3. 客户端使用示例

java 复制代码
/**
 * 单例模式客户端演示
 */
public class SingletonPatternDemo {
    public static void main(String[] args) {
        System.out.println("=== 单例模式完整演示 ===\n");
        
        // 演示1: 各种单例实现方式
        demonstrateSingletonImplementations();
        
        // 演示2: 配置管理器单例使用
        demonstrateConfigurationManager();
        
        // 演示3: 数据库连接池单例使用
        demonstrateDatabaseConnectionPool();
        
        // 演示4: 多线程环境测试
        demonstrateThreadSafety();
        
        // 演示5: 单例破坏与防护
        demonstrateSingletonProtection();
    }
    
    /**
     * 演示不同的单例实现方式
     */
    private static void demonstrateSingletonImplementations() {
        System.out.println("1. 不同单例实现方式演示:");
        System.out.println("-".repeat(50));
        
        // 饿汉式单例
        EagerSingleton eager1 = EagerSingleton.getInstance();
        EagerSingleton eager2 = EagerSingleton.getInstance();
        System.out.println("饿汉式单例是否相同: " + (eager1 == eager2));
        eager1.showMessage();
        
        // 懒汉式单例
        LazySingleton lazy1 = LazySingleton.getInstance();
        LazySingleton lazy2 = LazySingleton.getInstance();
        System.out.println("懒汉式单例是否相同: " + (lazy1 == lazy2));
        lazy1.showMessage();
        
        // 双重检查锁单例
        DoubleCheckedLockingSingleton dcl1 = DoubleCheckedLockingSingleton.getInstance();
        DoubleCheckedLockingSingleton dcl2 = DoubleCheckedLockingSingleton.getInstance();
        System.out.println("双重检查锁单例是否相同: " + (dcl1 == dcl2));
        dcl1.showMessage();
        
        // 静态内部类单例
        InnerClassSingleton inner1 = InnerClassSingleton.getInstance();
        InnerClassSingleton inner2 = InnerClassSingleton.getInstance();
        System.out.println("静态内部类单例是否相同: " + (inner1 == inner2));
        inner1.showMessage();
        
        // 枚举单例
        EnumSingleton enum1 = EnumSingleton.INSTANCE;
        EnumSingleton enum2 = EnumSingleton.INSTANCE;
        System.out.println("枚举单例是否相同: " + (enum1 == enum2));
        enum1.showMessage();
        enum1.doBusinessLogic();
    }
    
    /**
     * 演示配置管理器单例
     */
    private static void demonstrateConfigurationManager() {
        System.out.println("\n2. 配置管理器单例演示:");
        System.out.println("-".repeat(50));
        
        // 获取配置管理器实例
        ConfigurationManager configManager = ConfigurationManager.getInstance();
        
        // 读取配置
        System.out.println("应用名称: " + configManager.getConfig("app.name"));
        System.out.println("数据库URL: " + configManager.getConfig("database.url"));
        System.out.println("服务器端口: " + configManager.getConfig("server.port"));
        
        // 设置新配置
        configManager.setConfig("cache.enabled", "true");
        configManager.setConfig("max.connections", "100");
        
        // 打印所有配置
        configManager.printAllConfigs();
        
        // 验证单例特性
        ConfigurationManager anotherInstance = ConfigurationManager.getInstance();
        System.out.println("配置管理器单例验证: " + (configManager == anotherInstance));
    }
    
    /**
     * 演示数据库连接池单例
     */
    private static void demonstrateDatabaseConnectionPool() {
        System.out.println("\n3. 数据库连接池单例演示:");
        System.out.println("-".repeat(50));
        
        DatabaseConnectionPool pool1 = DatabaseConnectionPool.getInstance();
        DatabaseConnectionPool pool2 = DatabaseConnectionPool.getInstance();
        
        System.out.println("连接池单例验证: " + (pool1 == pool2));
        System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());
        System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());
        
        // 模拟获取和释放连接
        Connection conn1 = pool1.getConnection();
        Connection conn2 = pool1.getConnection();
        
        System.out.println("获取2个连接后:");
        System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());
        System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());
        
        pool1.releaseConnection(conn1);
        pool1.releaseConnection(conn2);
        
        System.out.println("释放2个连接后:");
        System.out.println("可用连接数: " + pool1.getAvailableConnectionsCount());
        System.out.println("已用连接数: " + pool1.getUsedConnectionsCount());
    }
    
    /**
     * 演示多线程环境下的单例安全性
     */
    private static void demonstrateThreadSafety() {
        System.out.println("\n4. 多线程安全性演示:");
        System.out.println("-".repeat(50));
        
        int threadCount = 5;
        Thread[] threads = new Thread[threadCount];
        
        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                InnerClassSingleton singleton = InnerClassSingleton.getInstance();
                System.out.println("线程 " + threadId + " 获取单例: " + singleton.hashCode());
            });
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("所有线程获取的单例哈希码相同,证明线程安全");
    }
    
    /**
     * 演示单例防护机制
     */
    private static void demonstrateSingletonProtection() {
        System.out.println("\n5. 单例防护机制演示:");
        System.out.println("-".repeat(50));
        
        try {
            // 尝试通过反射破坏单例
            // 注意:这里只是演示,实际代码应该避免这样的操作
            System.out.println("尝试通过反射创建实例...");
            
            // 对于枚举单例,反射也无法创建新实例
            // 对于其他实现,可以在构造函数中添加防护代码
        } catch (Exception e) {
            System.out.println("反射破坏被阻止: " + e.getMessage());
        }
    }
}

单例模式的优点

1. 严格控制实例数量

java 复制代码
// 确保整个应用中只有一个实例
ConfigurationManager config = ConfigurationManager.getInstance();
// 无论在哪里调用,得到的都是同一个实例

2. 全局访问点

java 复制代码
// 提供统一的访问入口
DatabaseConnectionPool pool = DatabaseConnectionPool.getInstance();
// 便于管理和维护

3. 延迟初始化

java 复制代码
// 静态内部类实现提供了延迟初始化的好处
// 只有在第一次调用getInstance()时才会创建实例

单例模式的缺点

1. 违反单一职责原则

java 复制代码
// 单例类同时负责创建实例和管理业务逻辑
public class Singleton {
    private static Singleton instance;
    
    // 业务方法
    public void businessMethod() { /* ... */ }
    
    // 单例管理方法
    public static Singleton getInstance() { /* ... */ }
}

2. 难以测试

java 复制代码
// 由于是全局状态,单元测试时难以mock
public class UserServiceTest {
    @Test
    public void testUserService() {
        // 单例的全局状态可能影响测试结果
        ConfigurationManager config = ConfigurationManager.getInstance();
        // 测试可能相互影响
    }
}

适用场景

  1. 需要频繁实例化然后销毁的对象
  2. 创建对象时耗时过多或耗资源过多,但又经常用到的对象
  3. 有状态的工具类对象
  4. 频繁访问数据库或文件的对象
  5. 资源共享的情况下,避免由于资源操作时导致的性能或损耗

最佳实践

1. 优先使用枚举或静态内部类实现

java 复制代码
// 推荐方式1:枚举单例
public enum EnumSingleton {
    INSTANCE;
    // 业务方法
}

// 推荐方式2:静态内部类
public class InnerClassSingleton {
    private static class Holder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    public static InnerClassSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

2. 考虑线程安全

java 复制代码
// 确保在多线程环境下也能正常工作
public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;
    
    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}

3. 防止反射和反序列化破坏

java 复制代码
public class ProtectedSingleton {
    private static final ProtectedSingleton INSTANCE = new ProtectedSingleton();
    
    private ProtectedSingleton() {
        // 防止反射攻击
        if (INSTANCE != null) {
            throw new RuntimeException("单例已被创建");
        }
    }
    
    // 防止反序列化破坏
    private Object readResolve() {
        return INSTANCE;
    }
}

总结

单例模式是设计模式中最简单但也最容易误用的模式之一。正确使用单例模式可以带来很多好处,但滥用也会导致很多问题。

核心价值:

  • 保证一个类只有一个实例
  • 提供全局访问点
  • 控制资源使用

使用建议:

  • 慎重考虑是否真的需要单例
  • 优先使用枚举或静态内部类实现
  • 注意线程安全问题
  • 考虑测试的便利性

选择指南:

  • 如果需要延迟加载:使用静态内部类或双重检查锁
  • 如果需要绝对防止反射攻击:使用枚举
  • 如果实例化开销小且一定会用到:使用饿汉式

掌握单例模式的正确使用方式,能够在合适的场景下大幅提升代码的质量和性能。

相关推荐
骄马之死1 天前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
zhengfei6111 天前
第3章 Agent 类型分类与设计模式
设计模式
刀法如飞1 天前
一文搞懂DDD 领域驱动设计思想原理
设计模式·架构·代码规范
郑洁文1 天前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code1 天前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
摇滚侠1 天前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
VidDown1 天前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
折哥的程序人生 · 物流技术专研1 天前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则
装不满的克莱因瓶1 天前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty