设计模式系列(04):单例模式(Singleton)

本文为设计模式系列第4篇,聚焦创建型模式中的单例模式,涵盖定义、原理、实际业务场景、优缺点、最佳实践及详细代码示例,适合系统学习与实战应用。


目录

  • [1. 模式概述](#1. 模式概述)
  • [2. 使用场景](#2. 使用场景)
  • [3. 优缺点分析](#3. 优缺点分析)
  • [4. 实际应用案例](#4. 实际应用案例)
  • [5. 结构与UML类图](#5. 结构与UML类图)
  • [6. 代码示例](#6. 代码示例)
  • [7. 测试用例](#7. 测试用例)
  • [8. 常见误区与反例](#8. 常见误区与反例)
  • [9. 最佳实践](#9. 最佳实践)
  • [10. 参考资料与延伸阅读](#10. 参考资料与延伸阅读)

1. 模式概述

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式主要用于控制资源的访问,确保资源的唯一性和一致性。通过私有构造方法和静态工厂方法,单例模式可以有效避免外部直接创建实例,常用于需要全局唯一对象的场景,防止资源浪费和状态不一致。

1.1 定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式通过私有化构造器、静态成员变量和静态工厂方法,确保系统中只存在一个该类的对象。它适用于需要全局唯一对象的场景,如配置管理、日志系统等,能够有效避免资源冲突和状态不一致。

1.2 目的

单例模式的核心目的是确保系统中某个类只有一个实例,避免重复创建带来的资源浪费。通过全局访问点,方便在系统各处获取同一个对象,保证状态一致性。此外,单例还能简化资源管理和配置维护,提升系统的健壮性。它不仅有助于统一管理有限资源,还能提升系统的可维护性和扩展性。

2. 使用场景

单例模式适用于以下场景:

  1. 资源共享:如配置管理、连接池、线程池等,多个模块需要共享同一份资源,单例模式可以避免重复创建和资源冲突。
  2. 全局状态:如计数器、日志记录、缓存管理等,全局状态需要被多个地方访问和修改,单例模式保证了数据的一致性和同步。
  3. 资源控制:如数据库连接、文件系统、设备管理等,通过单例模式集中管理资源,便于统一分配和回收,减少资源泄漏风险。
  4. 性能优化:如对象复用、内存优化、资源管理等,单例模式可以减少对象创建次数,提升系统性能,尤其适合重量级对象。

3. 优缺点分析

3.1 优点

  1. 资源控制:实例唯一,避免资源的重复分配和冲突,适合管理有限资源。统一的资源管理方式有助于系统的可维护性和扩展性。在多线程环境下,合理实现单例还能保证状态同步和一致性。
  2. 全局访问:单例对象可以被系统各处方便地访问,简化了依赖传递和对象管理。统一的全局入口有助于集中控制和监控,便于后期维护和调优。适合需要全局唯一状态或配置的场景,如日志、配置、缓存等。
  3. 延迟初始化:支持延迟加载,只有在真正需要时才创建实例,节省系统资源。这种按需分配的方式可以提升系统启动速度和运行效率。对于重量级对象,延迟初始化尤为重要,避免无谓的资源占用。

3.2 缺点

  1. 违反单一职责:单例类往往承担了过多的职责,既负责业务逻辑又负责实例管理,容易导致代码臃肿。全局访问点可能隐藏依赖关系,增加系统耦合,降低灵活性。单例难以进行单元测试和Mock,影响测试覆盖率和可维护性。
  2. 滥用风险:滥用单例会导致全局状态泛滥,增加调试和维护难度。隐式依赖使得代码难以理解和追踪,降低系统的可扩展性。建议仅在确有必要时使用单例,避免将其作为通用解决方案。
  3. 并发问题:在多线程环境下,单例实现不当容易引发线程安全问题,如重复创建、状态不一致等。加锁和同步会带来性能开销,影响系统吞吐量。需要合理选择实现方式,权衡安全性和性能。

4. 实际应用案例

  1. 配置管理:应用配置、系统设置、环境变量等,单例模式可集中管理和访问配置信息,保证全局一致。
  2. 资源管理:如数据库连接、线程池、缓存系统等,单例模式便于统一分配和回收资源,提升系统性能。
  3. 日志系统:日志记录、错误处理、性能监控等,单例模式可确保日志对象唯一,便于集中管理和分析。
  4. 设备控制:如打印机、扫描仪、网络设备等,单例模式可避免设备资源被多次初始化或冲突。

5. 结构与UML类图

plantuml 复制代码
@startuml
package "Singleton Pattern" #DDDDDD {
  class Singleton {
    - instance: Singleton
    - Singleton()
    + getInstance(): Singleton
    + doSomething(): void
  }
  class Client
  Singleton <.. Client : uses
}
@enduml

单例模式结构很简单,核心是私有构造器和静态实例获取方法。

6. 代码示例

6.1 基本结构

业务背景: 类加载时即创建实例,线程安全,适合对资源占用不敏感、全局频繁访问的场景。

java 复制代码
// 饿汉式单例(线程安全)
public class Singleton {
    // 静态成员变量,类加载时初始化
    private static final Singleton instance = new Singleton();
    // 私有构造方法,防止外部直接创建
    private Singleton() {}
    // 获取单例实例的全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

// 懒汉式单例(线程不安全)
// 线程安全性:不安全,仅适用于单线程环境。多线程下可能创建多个实例。
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// 懒汉式单例(线程安全)
// 线程安全性:安全,适合低并发场景。synchronized影响性能。
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// 双重检查锁单例
// 线程安全性:安全,兼顾性能,推荐高并发场景。需配合volatile。
public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

// 静态内部类单例
// 线程安全性:安全,延迟加载,推荐大多数场景。
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

// 枚举单例
// 线程安全性:安全,防止反射和序列化破坏,推荐极高安全要求场景。
public enum Singleton {
    INSTANCE;
    public void doSomething() {
        // 业务逻辑
    }
}

6.2 实际应用示例

java 复制代码
// 配置管理器单例
public class ConfigurationManager {
    // volatile保证多线程下可见性
    private static volatile ConfigurationManager instance;
    // 配置属性对象
    private Properties properties;
    // 私有构造方法,加载配置
    private ConfigurationManager() {
        loadConfiguration();
    }
    // 获取单例实例,双重检查锁保证线程安全和性能
    public static ConfigurationManager getInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = new ConfigurationManager();
                }
            }
        }
        return instance;
    }
    // 加载配置文件
    private void loadConfiguration() {
        properties = new Properties();
        try {
            properties.load(new FileInputStream("config.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 获取配置项
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
    // 设置配置项
    public void setProperty(String key, String value) {
        properties.setProperty(key, value);
    }
    // 保存配置到文件
    public void saveConfiguration() {
        try {
            properties.store(new FileOutputStream("config.properties"), "Updated configuration");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 数据库连接池单例
public class DatabaseConnectionPool {
    // volatile保证多线程下可见性
    private static volatile DatabaseConnectionPool instance;
    // 连接池列表
    private List<Connection> connections;
    // 最大连接数
    private int maxConnections;
    // 私有构造方法,初始化连接池
    private DatabaseConnectionPool() {
        maxConnections = 10;
        connections = new ArrayList<>();
        initializePool();
    }
    // 获取单例实例,双重检查锁保证线程安全
    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 < maxConnections; i++) {
            try {
                Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
                connections.add(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 获取连接,线程安全
    public synchronized Connection getConnection() {
        if (connections.isEmpty()) {
            throw new RuntimeException("No available connections");
        }
        return connections.remove(0);
    }
    // 释放连接,线程安全
    public synchronized void releaseConnection(Connection connection) {
        if (connections.size() < maxConnections) {
            connections.add(connection);
        }
    }
}

// 日志系统单例
public class Logger {
    private static final Logger instance = new Logger(); // 饿汉式,线程安全
    private Logger() {}
    public static Logger getInstance() {
        return instance;
    }
    public synchronized void log(String message) {
        // 简化:实际可写入文件/数据库等
        System.out.println("[LOG] " + message);
    }
}
// 用法示例:Logger.getInstance().log("系统启动");

7. 测试用例

java 复制代码
import org.junit.Test;
import static org.junit.Assert.*;

public class SingletonPatternTest {
    @Test
    public void testEagerSingleton() {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        assertSame(s1, s2);
    }
    @Test
    public void testLazySingleton() {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        assertSame(s1, s2);
    }
    @Test
    public void testEnumSingleton() {
        assertSame(Singleton.INSTANCE, Singleton.INSTANCE);
    }
}

8. 常见误区与反例

  • 误区1:多线程下实现不安全,导致多个实例。
    • 反例:未加锁的懒汉式单例在多线程下可能创建多个实例。
  • 误区2:滥用单例,导致全局状态混乱。
    • 单例应只用于确实需要全局唯一对象的场景。
  • 误区3:单例类承担过多职责,变成"万能类"。
    • 应保持单一职责,避免业务逻辑和实例管理混杂。

9. 最佳实践

  1. 推荐实现方式:优先使用静态内部类或枚举实现,保证线程安全和防止反射/序列化破坏。
  2. 职责单一:单例类应只负责自身的唯一性管理,避免承担过多业务逻辑。
  3. 线程安全:多线程环境下需确保单例实现的线程安全。
  4. 资源释放:如单例持有外部资源,应提供资源释放方法。
  5. 文档同步:保持文档、UML和代码示例一致,便于团队协作。

10. 参考资料与延伸阅读


本文为设计模式系列第4篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。

相关推荐
张小姐的猫3 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充6 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充6 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充6 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充6 小时前
2.面向对象的特性
设计模式
杨充6 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充6 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充6 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯9 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法