单例设计模式:开发者完整指南

发布: (2026年2月6日 GMT+8 19:17)
8 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容,我将按照要求保留源链接并将文本翻译成简体中文。

介绍

我们都有过这样的经历:在项目开发中,突然发现整个应用程序只能拥有 恰好一个实例 的类。常见的例子包括数据库连接池、配置管理器或日志服务。创建多个实例既浪费资源,又会导致混乱,甚至带来严重的风险。

这时,单例模式 就能帮你解决这个问题。

什么是单例模式?

一种设计模式,限制一个类的实例化为单个对象,并提供对该对象的全局访问点。

基本实现

public class DatabaseConnection {
    // Static variable to hold the single instance
    private static DatabaseConnection instance;

    // Private constructor prevents instantiation from other classes
    private DatabaseConnection() {
        System.out.println("Database connection established");
    }

    // Public method to provide access to the instance
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    public void executeQuery(String query) {
        System.out.println("Executing: " + query);
    }
}

用法

public class Application {
    public static void main(String[] args) {
        DatabaseConnection db1 = DatabaseConnection.getInstance();
        DatabaseConnection db2 = DatabaseConnection.getInstance();

        System.out.println(db1 == db2); // Output: true (same instance)
    }
}

问题: 这个基本版本 不是线程安全的。在多线程环境中,两个线程如果同时调用 getInstance(),可能会创建不同的实例。

线程安全的单例(饿汉式)

public class ConfigurationManager {
    // Instance created at class loading time
    private static final ConfigurationManager instance = new ConfigurationManager();

    private ConfigurationManager() {
        // Load configuration from file
        System.out.println("Loading configuration...");
    }

    public static ConfigurationManager getInstance() {
        return instance;
    }

    public String getProperty(String key) {
        // Return configuration property
        return "value_for_" + key;
    }
}

优点: 线程安全,因为实例在类加载时就已创建。
缺点: 没有懒加载——即使从未使用,实例也会被创建。

线程安全的单例(懒加载与双重检查锁定)

public class Logger {
    private static volatile Logger instance;

    private Logger() {
        System.out.println("Logger initialized");
    }

    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

关键点

  • volatile 确保在各线程之间的可见性。
  • 双重检查锁定将同步开销降到最低。

Bill Pugh Singleton(推荐方法)

public class CacheManager {

    private CacheManager() {
        System.out.println("Cache Manager initialized");
    }

    // Static inner class – not loaded until referenced
    private static class SingletonHelper {
        private static final CacheManager INSTANCE = new CacheManager();
    }

    public static CacheManager getInstance() {
        return SingletonHelper.INSTANCE;
    }

    public void put(String key, Object value) {
        System.out.println("Caching: " + key);
    }

    public Object get(String key) {
        return "cached_value_for_" + key;
    }
}

为什么它更受推荐

  • 线程安全。
  • 支持懒加载(延迟初始化)。
  • 不需要显式同步。

枚举单例(防止反射)

public enum ApplicationContext {
    INSTANCE;

    private String environment;

    ApplicationContext() {
        this.environment = "production";
        System.out.println("Application Context initialized");
    }

    public String getEnvironment() {
        return environment;
    }

    public void setEnvironment(String environment) {
        this.environment = environment;
    }
}

用法

ApplicationContext context = ApplicationContext.INSTANCE;
context.setEnvironment("development");

优势

  • 自动提供序列化和线程安全。
  • 防御反射攻击。

Where to Use the Singleton Pattern

  • Configuration Management – 应用设置只加载一次并在全局访问。
  • Logger Classes – 集中式日志记录机制。
  • Database Connection Pools – 管理可重用连接的池。
  • Cache Managers – 内存缓存系统。
  • Thread Pools – 管理工作线程。
  • Device Drivers – 访问硬件资源(例如,打印机)。

实际案例

public class AppConfig {
    private static class SingletonHelper {
        private static final AppConfig INSTANCE = new AppConfig();
    }

    private Properties properties;

    private AppConfig() {
        properties = new Properties();
        loadConfiguration();
    }

    private void loadConfiguration() {
        // Load from file, environment variables, etc.
        properties.setProperty("app.name", "MyApplication");
        properties.setProperty("max.connections", "100");
        properties.setProperty("timeout", "30000");
    }

    public static AppConfig getInstance() {
        return SingletonHelper.INSTANCE;
    }

    public String get(String key) {
        return properties.getProperty(key);
    }
}

单例模式的优势

  • 受控访问 – 仅存在一个实例,确保整个应用程序的状态保持一致。
  • 内存效率 – 避免创建多个占用大量内存的对象。
  • 全局访问点 – 在任何位置都能轻松访问,无需传递引用。
  • 懒加载 – (使用懒加载方式时)实例仅在需要时才创建。

缺点与陷阱

  • 测试挑战 – 单例引入全局状态,使单元测试变得困难。无法轻松地模拟或替换实例。

    // Difficult to test
    public class OrderService {
        public void processOrder(Order order) {
            Logger.getInstance().log("Processing order: " + order.getId());
            // Hard to verify logging behavior in tests
        }
    }
  • 隐藏的依赖 – 使用单例的类没有显式声明其依赖,违反了依赖倒置原则。

  • 违反单一职责原则 – 类既管理核心功能,又负责实例创建。

  • 全局状态 – 可能导致紧耦合,使代码更难理解。

  • 并发问题 – 如果实现不当,可能导致竞争条件。

  • 序列化问题 – 需要特殊处理以防止在反序列化时创建多个实例。

何时避免使用单例

  • 未来可能需要多个实例(即使你现在认为只需要一个)。
  • 类具有可变状态,应用的不同部分会修改它。
  • 你在编写可测试的代码,需要注入依赖。
  • 对象很轻量,创建多个实例没有问题。
  • 你在分布式系统中工作,“单实例”概念模糊。

更好的替代方案

  • 依赖注入框架 – Spring、Guice 等。
  • 工厂模式 与实例管理。
  • 静态工具类(用于无状态操作)。

如何识别合适的场景

自问以下问题:

  1. 我真的只需要一个实例吗?
    诚实面对。“每个应用程序一个实例” 与 “我觉得一个就够了” 是不同的。

  2. 这是否是必须共享的资源?
    数据库连接池:是。
    用户会话:否。

  3. 这个全局状态会导致问题吗?
    如果应用的不同部分需要不同的配置,单例并不是答案。

  4. 我能轻松测试它吗?
    如果单例让测试变得痛苦,考虑使用依赖注入。

  5. 这是一个无状态的工具类吗?
    如果是,你根本可能不需要单例——只需使用静态方法即可。

最终思考

关键是了解你的具体使用场景。它是真正的共享资源吗?拥有一个实例真的会对你的应用程序有益吗?你能有效地进行测试吗?

请记住,设计模式是 指南,而非规则。最佳代码能够清晰、可维护且高效地解决你的问题。有时这就是单例模式;但大多数情况下并不是。

祝编码愉快!

Back to Blog

相关文章

阅读更多 »

Java 特性::

Java 编程语言最初是为嵌入式系统、机顶盒和电视而开发的。根据需求,它被设计为能够在各种 p...

Java的特性

Java 特性封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.am...