设计模式之:单例模式

文章目录

    • 什么是单例模式?
    • 核心思想
    • 模式结构
    • 单例模式的多种实现方式
      • [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;
    }
}

总结

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

核心价值:

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

使用建议:

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

选择指南:

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

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

相关推荐
消失的旧时光-19434 小时前
@JvmStatic 的作用
java·开发语言·kotlin
火锅机器5 小时前
java 8 lambda表达式对list进行分组
java·开发语言·list
我是华为OD~HR~栗栗呀5 小时前
华为od-22届考研-测试面经
java·c++·python·功能测试·华为od·华为·面试
是梦终空5 小时前
计算机毕业设计241—基于Java+Springboot+vue的爱心公益服务系统(源代码+数据库+11000字文档)
java·spring boot·vue·毕业设计·课程设计·毕业论文·爱心公益系统
_殊途5 小时前
项目开发手册-项目结构
java
keke_俩个科5 小时前
ShardingSphere分库分表基础配置与使用说明
java·数据库·分布式·spring
爬虫程序猿5 小时前
把“天猫”装进 JVM:Java 关键词商品爬虫从 0 到 1(含完整可运行代码)
java·jvm·爬虫
java_logo5 小时前
Docker 部署微服务项目保姆级教程
java·运维·docker·微服务·容器·eureka·架构
oak隔壁找我6 小时前
Servlet 三大组件详解
java·后端