【设计模式手册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版权协议,转载请附上原文出处链接及本声明。

相关推荐
毕设源码-赖学姐5 分钟前
【开题答辩全过程】以 基于Springboot的智慧养老系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
jamesge20107 分钟前
限流之漏桶算法
java·开发语言·算法
jvstar8 分钟前
JAVA面试题和答案
java
冷雨夜中漫步9 分钟前
OpenAPITools使用——FAQ
android·java·缓存
9坐会得自创14 分钟前
使用marked将markdown渲染成HTML的基本操作
java·前端·html
Hello.Reader35 分钟前
Flink ML 线性 SVM(Linear SVC)入门输入输出列、训练参数与 Java 示例解读
java·支持向量机·flink
oioihoii35 分钟前
C++数据竞争与无锁编程
java·开发语言·c++
最贪吃的虎35 分钟前
什么是开源?小白如何快速学会开源协作流程并参与项目
java·前端·后端·开源
资生算法程序员_畅想家_剑魔36 分钟前
Java常见技术分享-16-多线程安全-并发编程的核心问题
java·开发语言
We....36 分钟前
Java SPI 机制
java·开发语言