单例模式
引言
单例模式是一种经典的创建型设计模式,旨在确保一个类在整个应用程序生命周期中仅存在一个实例,并提供全局访问点。它解决了资源浪费、并发冲突等问题,在实际开发中被广泛使用。
诞生背景
单例模式的概念最早由《设计模式:可复用面向对象软件的基础》一书提出。虽然书中未将其作为独立章节讨论,但其属于 创建型模式
重要组成部分。随着面向对象编程的发展,单例模式在共享资源管理方面展现出独特优势,逐渐成为构建系统架构的关键手段之一。
演进过程
从最初的简单实现到线程安全版本,再到现代语言特性(如枚举)支持,单例模式经历了多个阶段的演进。开发者不断改进其实现方式,以应对多线程、懒加载、反序列化等挑战。
核心概念
定义:保证一个类有且仅有一个实例,并提供一个全局访问入口。
特点:
- 唯一性:整个应用中只存在一个实例。
- 全局访问性:通过静态方法获取该实例。
- 延迟加载性:实例在第一次使用时才被创建(非强制)。
通用实现
Java 实现
1. 饿汉式(静态常量)
// 饿汉式(静态变量)
class Singleton {
// 构造方法私有化,不能在外部实例化
private Singleton() {
}
// 创建实例对象
private final static Singleton instance = new Singleton();
// 返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优点:在类加载的时候就完成了实例化,避免了线程同步问题
缺点:没有实现懒加载
2. 饿汉式(静态代码块)
// 饿汉式(静态代码块)
class Singleton {
// 构造方法私有化,不能在外部实例化
private Singleton() {
}
private static Singleton instance;
// 创建实例对象
static {
instance = new Singleton();
}
// 返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优点:静态代码块里面的代码在类加载时就会执行,此时已经完成了实例化,避免了线程同步问题
缺点:没有实现懒加载
3. 懒汉式(线程不安全)
// 懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:实现了懒加载
缺点:线程不安全。单线程的情况下可以使用
4. 懒汉式(同步方法)
// 懒汉式(同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:解决了线程不安全问题
缺点:效率低,synchronized锁住了整个方法,每次访问该方法,都会锁住整个方法,但是,instance实例在第一次访问的时候就已经实例化,后续只需要直接返回,没有线程安全的问题,也就不需要每次都锁住整个方法,导致方法的效率降低
5. 懒汉式(同步代码块)
// 懒汉式(同步代码块)
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
优点:相比懒汉式(同步方法),效率有了一定的提高
缺点:线程不安全
6. 双重检查
// 双重检查
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. 静态内部类
// 静态内部类
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. 枚举
// 枚举
enum Singleton {
INSTANCE;
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:避免了线程安全问题,还能防止反序列化重新创建新的对象
php实现
1. 简单实现
<?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 示例:
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 框架
@Service
@Scope("singleton")
public class MyService {
// 业务逻辑
}
当需要使用该单例时,直接通过依赖注入获取:
@Autowired
private MyService myService;
这种方式的优势包括:
- 解耦:调用者无需关心实例创建过程;
- 可配置性:通过配置文件或注解即可改变实例行为;
- 生命周期管理:由框架自动管理对象的初始化和销毁;
- 集成便利:与 Spring 等现代框架无缝集成,便于构建复杂系统。
Laravel 框架
在 Laravel 中,可以通过服务容器绑定一个单例,确保每次解析时都返回同一个实例。
绑定单例:
// 在服务提供者中绑定单例
$this->app->singleton('MyApp\Services\MyService', function ($app) {
return new MyService();
});
使用单例:
// 通过构造函数注入
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()
来手动获取:
$myService = app()->make('MyApp\Services\MyService');
优势:
- 自动解析依赖:Laravel 容器会自动解析类的所有依赖项;
- 集中管理:所有单例绑定集中在一个地方,易于维护;
- 环境隔离:可以在不同环境中绑定不同的实现;
- 生命周期可控:确保整个请求周期内为唯一实例。
多例模式
此外,单例模式还可以进一步演进为 多例模式(Multiton Pattern),它是对单例模式的一种扩展,允许可控地创建多个实例,并通过键值进行访问和管理。
定义:多例模式是一种创建型设计模式,确保一个类的实例只能有有限个,并且这些实例可以通过一个唯一的标识符进行访问。它在保留可控实例化的同时,允许系统拥有多个特定的实例。
特点:
- 每个实例由唯一键标识;
- 实例数量有限制;
- 提供统一的访问接口;
- 支持懒加载机制。
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);
}
}
使用方式:
Multiton instanceA = Multiton.getInstance("A");
Multiton instanceB = Multiton.getInstance("B");
应用场景
- 数据库连接池:根据不同的数据源名称维护多个连接实例;
- 日志记录器:按模块或级别划分多个日志实例;
- 配置中心:根据不同环境(dev、test、prod)提供各自的配置实例;
- 资源管理器:如线程池、缓存池等需要多组独立资源的场景。
优点
- 控制实例数量,避免无限制创建;
- 提供统一的访问入口;
- 支持按需创建与管理;
- 增强灵活性与可扩展性。
缺点
- 增加了管理实例的复杂度;
- 若键管理不当,可能导致内存泄漏;
- 多线程环境下仍需同步处理。
与单例模式对比
特性 | 单例模式 | 多例模式 |
---|---|---|
实例数量 | 1 | 可控多个 |
访问方式 | 全局唯一入口 | 键值访问 |
管理复杂度 | 低 | 中 |
适用场景 | 全局共享资源 | 多组受控资源 |
模式协作
单例模式作为创建型设计模式,常与其他设计模式协同工作,以构建更复杂、灵活、可维护的系统结构。通过与其他模式的配合,可以提升系统的解耦程度、增强扩展性,并优化资源管理。
单例 + 工厂模式
作用:通过工厂统一创建单例对象,实现对实例创建逻辑的封装。
优势:
- 将创建逻辑集中化;
- 提高扩展性,便于替换不同实现;
- 支持依赖注入或配置驱动的初始化方式。
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...");
}
}
调用方式:
Service serviceA = ServiceFactory.getService("A");
serviceA.execute(); // 输出:Executing Service A...
Service serviceB = ServiceFactory.getService("B");
serviceB.execute(); // 输出:Executing Service B...
单例 + 代理模式
作用:为单例对象提供访问控制、日志记录、权限验证等功能。
优势:
- 控制对单例的访问;
- 增加额外功能而不修改原类;
- 实现远程调用、缓存等增强行为。
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();
}
}
使用方式:
Database db = new DatabaseProxy();
db.connect(); // 输出:Logging connection request... Connecting to database...
单例 + 观察者模式
作用:用于监听并响应单例状态的变化,适用于全局状态通知机制。
优势:
- 解耦发布者与订阅者;
- 支持广播通知机制;
- 适用于事件驱动架构。
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);
}
}
使用方式:
AppState appState = AppState.getInstance();
appState.addObserver(new LoggerObserver());
appState.setState("RUNNING");
// 输出:State changed to: RUNNING
单例 + 策略模式
作用:在运行时动态切换单例的行为策略。
优势:
- 提供算法或行为的可插拔性;
- 避免冗长的条件判断语句;
- 可结合配置文件动态加载策略。
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);
}
}
使用方式:
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.
总结
模式 | 合作目的 | 典型应用场景 |
---|---|---|
工厂模式 | 封装单例创建逻辑 | 统一创建、多实现支持 |
代理模式 | 控制访问或增强功能 | 日志、权限控制、远程调用 |
观察者模式 | 监听状态变化 | 全局状态同步、事件通知 |
策略模式 | 动态切换行为 | 多种算法共存、运行时切换 |
延伸思考
- 如何测试单例类?
- 使用反射机制访问私有构造函数进行实例化比较
- 验证多次获取实例是否为同一个对象(使用JUnit/TestNG/PHPUnit)
- 检查线程安全性和唯一性
- 使用Mock框架(如Mockito)进行模拟测试
- 如何打破单例限制?
- 利用反射调用私有构造函数创建新实例
- 通过反序列化创建新实例(如果没有实现readResolve方法)
- 在类加载器不同的情况下创建多个实例
- 修改单例实现使其支持多例模式
- 在微服务架构中是否仍适用?
- 单例模式在单个微服务内部仍然适用
- 跨微服务场景需要考虑分布式单例模式
- 可以结合注册中心实现服务级别的全局访问
- 根据具体需求选择本地单例或集群单例实现
总结
单例模式作为一种常见的设计模式,解决了类实例化过多的问题,提供了全局访问点,减少了资源浪费,是解决资源共享与实例控制的经典方案。然而,单例模式也并非适用于所有场景。在使用时,开发者需要权衡其优缺点,避免过度设计和引入不必要的复杂性。未来,随着软件架构向微服务、云计算等现代开发模式转型,单例模式在这些新兴领域中的应用仍然值得关注。