Skip to content

单例模式

引言

单例模式是一种经典的创建型设计模式,旨在确保一个类在整个应用程序生命周期中仅存在一个实例,并提供全局访问点。它解决了资源浪费、并发冲突等问题,在实际开发中被广泛使用。

诞生背景

单例模式的概念最早由《设计模式:可复用面向对象软件的基础》一书提出。虽然书中未将其作为独立章节讨论,但其属于 创建型模式 重要组成部分。随着面向对象编程的发展,单例模式在共享资源管理方面展现出独特优势,逐渐成为构建系统架构的关键手段之一。

演进过程

从最初的简单实现到线程安全版本,再到现代语言特性(如枚举)支持,单例模式经历了多个阶段的演进。开发者不断改进其实现方式,以应对多线程、懒加载、反序列化等挑战。

核心概念

定义:保证一个类有且仅有一个实例,并提供一个全局访问入口。

特点

  • 唯一性:整个应用中只存在一个实例。
  • 全局访问性:通过静态方法获取该实例。
  • 延迟加载性:实例在第一次使用时才被创建(非强制)。

通用实现

Java 实现

1. 饿汉式(静态常量)

java
// 饿汉式(静态变量)
class Singleton {
    // 构造方法私有化,不能在外部实例化
    private Singleton() {
    }

    // 创建实例对象
    private final static Singleton instance = new Singleton();

    // 返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

优点:在类加载的时候就完成了实例化,避免了线程同步问题

缺点:没有实现懒加载

2. 饿汉式(静态代码块)

java
// 饿汉式(静态代码块)
class Singleton {
    // 构造方法私有化,不能在外部实例化
    private Singleton() {
    }

    private static Singleton instance;

    // 创建实例对象
    static {
        instance = new Singleton();
    }

    // 返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

优点:静态代码块里面的代码在类加载时就会执行,此时已经完成了实例化,避免了线程同步问题

缺点:没有实现懒加载

3. 懒汉式(线程不安全)

java
// 懒汉式(线程不安全)
class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点:实现了懒加载

缺点:线程不安全。单线程的情况下可以使用

4. 懒汉式(同步方法)

java
// 懒汉式(同步方法)
class Singleton {
	private static Singleton instance;

	private Singleton() {
	}

	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

优点:解决了线程不安全问题

缺点:效率低,synchronized锁住了整个方法,每次访问该方法,都会锁住整个方法,但是,instance实例在第一次访问的时候就已经实例化,后续只需要直接返回,没有线程安全的问题,也就不需要每次都锁住整个方法,导致方法的效率降低

5. 懒汉式(同步代码块)

java
// 懒汉式(同步代码块)
class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

优点:相比懒汉式(同步方法),效率有了一定的提高

缺点:线程不安全

6. 双重检查

java
// 双重检查
class Singleton {
	private static volatile Singleton instance;

	private Singleton() {
	}

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

优点:加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时解决了效率问题

7. 静态内部类

java
// 静态内部类
class Singleton {
	private Singleton() {
	}

	private static class SingletonInstance {
		private static final Singleton INSTANCE = new Singleton();
	}

	public static synchronized Singleton getInstance() {

		return SingletonInstance.INSTANCE;
	}
}

优点:通过类加载机制解决了线程安全问题,在Singleton类被装载时并不会立即实例化,而是在需要实例化,实现了懒加载的效果

8. 枚举

java
// 枚举
enum Singleton {
	INSTANCE;

	public static Singleton getInstance() {
		return INSTANCE;
	}
}

优点:避免了线程安全问题,还能防止反序列化重新创建新的对象

php实现

1. 简单实现

php
<?php

declare(strict_types=1);

use RuntimeException;

// 单例类
class Singleton
{
    // 实例
    private static $instance;

    // 禁止new初始化
    private function __construct()
    {
    }

    // 获取实例
    public static function getInstance(): self
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // 防止克隆
    private function __clone()
    {
    }

    // 防止反序列化
    public function __wakeup()
    {
        if (self::$instance !== null) {
            throw new RuntimeException('反序列化不被允许');
        }

        self::$instance = $this;
    }
}

应用场景

  • 数据库连接池:避免重复建立连接,提高效率。
  • 配置管理器:统一读取与管理配置信息。
  • 日志记录器:集中处理日志输出。
  • 缓存服务:提供统一的缓存访问接口。

案例分析

以下是一个日志记录器的 Java 示例:

java
public class Logger {
    private static Logger instance;

    private Logger() {}

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

    public void log(String message) {
        System.out.println("Log message: " + message);
    }
}

优点

  • 控制实例数量,避免资源浪费;
  • 提供全局访问点,简化调用逻辑;
  • 支持延迟加载,提升启动性能。

缺点

  • 全局状态可能导致维护困难;
  • 过度使用可能造成系统耦合;
  • 多线程环境下需额外处理线程安全问题。

扩展

依赖注入容器

除了标准实现外,还可以结合依赖注入容器(如 Spring 的 @Scope("singleton"))来实现更灵活的单例管理。在现代框架中,依赖注入机制使得单例的定义和使用更加简洁、解耦,并由框架统一管理其生命周期。

Spring 框架

java
@Service
@Scope("singleton")
public class MyService {
    // 业务逻辑
}

当需要使用该单例时,直接通过依赖注入获取:

java
@Autowired
private MyService myService;

这种方式的优势包括:

  • 解耦:调用者无需关心实例创建过程;
  • 可配置性:通过配置文件或注解即可改变实例行为;
  • 生命周期管理:由框架自动管理对象的初始化和销毁;
  • 集成便利:与 Spring 等现代框架无缝集成,便于构建复杂系统。

Laravel 框架

在 Laravel 中,可以通过服务容器绑定一个单例,确保每次解析时都返回同一个实例。

绑定单例:

php
// 在服务提供者中绑定单例
$this->app->singleton('MyApp\Services\MyService', function ($app) {
    return new MyService();
});

使用单例:

php
// 通过构造函数注入
use MyApp\Services\MyService;

class MyController extends Controller
{
    protected $myService;

    public function __construct(MyService $myService)
    {
        $this->myService = $myService;
    }

    public function index()
    {
        $this->myService->doSomething();
    }
}

或者也可以通过 Facade 或辅助函数 app() 来手动获取:

php
$myService = app()->make('MyApp\Services\MyService');

优势:

  • 自动解析依赖:Laravel 容器会自动解析类的所有依赖项;
  • 集中管理:所有单例绑定集中在一个地方,易于维护;
  • 环境隔离:可以在不同环境中绑定不同的实现;
  • 生命周期可控:确保整个请求周期内为唯一实例。

多例模式

此外,单例模式还可以进一步演进为 多例模式(Multiton Pattern),它是对单例模式的一种扩展,允许可控地创建多个实例,并通过键值进行访问和管理。

定义:多例模式是一种创建型设计模式,确保一个类的实例只能有有限个,并且这些实例可以通过一个唯一的标识符进行访问。它在保留可控实例化的同时,允许系统拥有多个特定的实例。

特点

  • 每个实例由唯一键标识;
  • 实例数量有限制;
  • 提供统一的访问接口;
  • 支持懒加载机制。

Java 示例

java
class Multiton {
    // 存储不同实例的容器
    private static final Map<String, Multiton> instances = new HashMap<>();

    // 私有构造函数
    private Multiton() {}

    // 获取指定键的实例
    public static synchronized Multiton getInstance(String key) {
        if (!instances.containsKey(key)) {
            instances.put(key, new Multiton());
        }
        return instances.get(key);
    }
}

使用方式

java
Multiton instanceA = Multiton.getInstance("A");
Multiton instanceB = Multiton.getInstance("B");

应用场景

  • 数据库连接池:根据不同的数据源名称维护多个连接实例;
  • 日志记录器:按模块或级别划分多个日志实例;
  • 配置中心:根据不同环境(dev、test、prod)提供各自的配置实例;
  • 资源管理器:如线程池、缓存池等需要多组独立资源的场景。

优点

  • 控制实例数量,避免无限制创建;
  • 提供统一的访问入口;
  • 支持按需创建与管理;
  • 增强灵活性与可扩展性。

缺点

  • 增加了管理实例的复杂度;
  • 若键管理不当,可能导致内存泄漏;
  • 多线程环境下仍需同步处理。

与单例模式对比

特性单例模式多例模式
实例数量1可控多个
访问方式全局唯一入口键值访问
管理复杂度
适用场景全局共享资源多组受控资源

模式协作

单例模式作为创建型设计模式,常与其他设计模式协同工作,以构建更复杂、灵活、可维护的系统结构。通过与其他模式的配合,可以提升系统的解耦程度、增强扩展性,并优化资源管理。

单例 + 工厂模式

作用:通过工厂统一创建单例对象,实现对实例创建逻辑的封装。

优势

  • 将创建逻辑集中化;
  • 提高扩展性,便于替换不同实现;
  • 支持依赖注入或配置驱动的初始化方式。

Java 示例

java
class ServiceFactory {
    private static final Map<String, Service> instances = new HashMap<>();
    private static final Object lock = new Object();

    public static Service getService(String type) {
        synchronized (lock) {
            if (!instances.containsKey(type)) {
                switch (type) {
                    case "A":
                        instances.put(type, new ServiceAImpl());
                        break;
                    case "B":
                        instances.put(type, new ServiceBImpl());
                        break;
                    default:
                        throw new IllegalArgumentException("Unknown service type: " + type);
                }
            }
            return instances.get(type);
        }
    }
}

interface Service {
    void execute();
}

class ServiceAImpl implements Service {
    public void execute() {
        System.out.println("Executing Service A...");
    }
}

class ServiceBImpl implements Service {
    public void execute() {
        System.out.println("Executing Service B...");
    }
}

调用方式

java
Service serviceA = ServiceFactory.getService("A");
serviceA.execute(); // 输出:Executing Service A...

Service serviceB = ServiceFactory.getService("B");
serviceB.execute(); // 输出:Executing Service B...

单例 + 代理模式

作用:为单例对象提供访问控制、日志记录、权限验证等功能。

优势

  • 控制对单例的访问;
  • 增加额外功能而不修改原类;
  • 实现远程调用、缓存等增强行为。

Java 示例

java
interface Database {
    void connect();
}

class RealDatabase implements Database {
    public void connect() {
        System.out.println("Connecting to database...");
    }
}

class DatabaseProxy implements Database {
    private Database realDb;
    
    public void connect() {
        if (realDb == null) {
            realDb = new RealDatabase();
        }
        System.out.println("Logging connection request...");
        realDb.connect();
    }
}

使用方式

java
Database db = new DatabaseProxy();
db.connect(); // 输出:Logging connection request... Connecting to database...

单例 + 观察者模式

作用:用于监听并响应单例状态的变化,适用于全局状态通知机制。

优势

  • 解耦发布者与订阅者;
  • 支持广播通知机制;
  • 适用于事件驱动架构。

Java 示例

java
import java.util.ArrayList;
import java.util.List;

class AppState {
    private static volatile AppState instance;
    private String state;
    private List<Observer> observers = new ArrayList<>();

    private AppState() {}

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

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

interface Observer {
    void update(String state);
}

class LoggerObserver implements Observer {
    public void update(String state) {
        System.out.println("State changed to: " + state);
    }
}

使用方式

java
AppState appState = AppState.getInstance();
appState.addObserver(new LoggerObserver());
appState.setState("RUNNING");
// 输出:State changed to: RUNNING

单例 + 策略模式

作用:在运行时动态切换单例的行为策略。

优势

  • 提供算法或行为的可插拔性;
  • 避免冗长的条件判断语句;
  • 可结合配置文件动态加载策略。

Java 示例

java
interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid $" + amount + " by credit card.");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid $" + amount + " via PayPal.");
    }
}

class PaymentProcessor {
    private static volatile PaymentProcessor instance;
    private PaymentStrategy strategy;

    private PaymentProcessor(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public static PaymentProcessor getInstance(PaymentStrategy strategy) {
        if (instance == null) {
            synchronized (PaymentProcessor.class) {
                if (instance == null) {
                    instance = new PaymentProcessor(strategy);
                }
            }
        } else {
            instance.strategy = strategy; // 动态更新策略
        }
        return instance;
    }

    public void process(int amount) {
        strategy.pay(amount);
    }
}

使用方式

java
PaymentProcessor processor = PaymentProcessor.getInstance(new CreditCardPayment());
processor.process(100); // 输出:Paid $100 by credit card.

processor = PaymentProcessor.getInstance(new PayPalPayment());
processor.process(50); // 输出:Paid $50 via PayPal.

总结

模式合作目的典型应用场景
工厂模式封装单例创建逻辑统一创建、多实现支持
代理模式控制访问或增强功能日志、权限控制、远程调用
观察者模式监听状态变化全局状态同步、事件通知
策略模式动态切换行为多种算法共存、运行时切换

延伸思考

  1. 如何测试单例类?
  • 使用反射机制访问私有构造函数进行实例化比较
  • 验证多次获取实例是否为同一个对象(使用JUnit/TestNG/PHPUnit)
  • 检查线程安全性和唯一性
  • 使用Mock框架(如Mockito)进行模拟测试
  1. 如何打破单例限制?
  • 利用反射调用私有构造函数创建新实例
  • 通过反序列化创建新实例(如果没有实现readResolve方法)
  • 在类加载器不同的情况下创建多个实例
  • 修改单例实现使其支持多例模式
  1. 在微服务架构中是否仍适用?
  • 单例模式在单个微服务内部仍然适用
  • 跨微服务场景需要考虑分布式单例模式
  • 可以结合注册中心实现服务级别的全局访问
  • 根据具体需求选择本地单例或集群单例实现

总结

单例模式作为一种常见的设计模式,解决了类实例化过多的问题,提供了全局访问点,减少了资源浪费,是解决资源共享与实例控制的经典方案。然而,单例模式也并非适用于所有场景。在使用时,开发者需要权衡其优缺点,避免过度设计和引入不必要的复杂性。未来,随着软件架构向微服务、云计算等现代开发模式转型,单例模式在这些新兴领域中的应用仍然值得关注。