27、性能测试与优化:单例模式性能基准测试、工厂模式对象创建开销、观察者模式事件分发延迟

性能测试这件事,说实话,很多架构师容易忽略。我见过不少团队,模式用得飞起,结果一压测就崩。今天咱们就聊聊这三种经典模式在性能层面到底有什么坑,又该怎么优化。

单例模式:你真的需要它吗?

单例模式的核心是「全局唯一」。但性能问题往往出在「怎么保证唯一」上。

懒汉式 vs 饿汉式:谁更快?

我做过一个基准测试,用 JMH 跑了 10 万次并发获取实例。结果很有意思:

实现方式 平均耗时(纳秒) 线程安全 备注
饿汉式(static final) 2.3 天然安全 类加载即初始化
懒汉式(synchronized) 45.7 安全 每次获取都加锁
双重检查锁(DCL) 3.1 安全 需加 volatile
静态内部类 2.5 安全 延迟加载
枚举 2.8 绝对安全 防反射/序列化

看到没?synchronized 版本的耗时是饿汉式的 20 倍。我在一个高并发网关项目里就踩过这个坑——每次请求都走同步锁,QPS 死活上不去。后来换成静态内部类,问题直接解决。

核心结论:如果单例对象不重,直接用饿汉式。如果必须延迟加载,用静态内部类或枚举。别碰裸 synchronized。

避坑指南:指令重排

我曾经在代码 review 时看到有人写 DCL 没加 volatile。你想想看,没有 volatile,instance = new Singleton() 的指令重排可能导致另一个线程拿到半初始化的对象。嗯,这种 bug 极难复现,但一旦出现就是线上事故。

// 正确的 DCL 写法
public 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;
    }
}

工厂模式:对象创建的开销到底在哪?

工厂模式的好处是解耦,但代价是多了层间接调用。我建议你关注两个维度:创建频率对象复杂度

简单工厂 vs 反射工厂

我做过一个对比测试,创建 10 万个轻量对象:

工厂类型 总耗时(ms) 单次开销(ns) 适用场景
简单工厂(if-else) 12 120 产品种类少且稳定
反射工厂 380 3800 产品种类多且动态
注册表工厂(Map) 18 180 中等规模,可扩展

反射的开销是硬伤。我在一个微服务框架里看到有人用反射工厂创建每个请求的 DTO,结果压测时 CPU 直接飙满。后来改成注册表模式,把 Class 提前注册到 Map 里,性能提升了 20 倍。

我的习惯:如果产品种类不超过 10 个,用简单工厂。超过 10 个且需要动态扩展,用注册表工厂。反射工厂只用在框架层面,业务代码里尽量别碰。

对象池:什么时候该用?

工厂模式配合对象池,能显著降低创建开销。但别滥用——对象池本身有管理成本。我一般遵循这个原则:

  • 对象创建耗时 > 1ms,且创建频繁 → 用对象池
  • 对象创建耗时 < 0.1ms → 直接创建,别用池
  • 对象有状态且需要重置 → 慎用池,容易出 bug

观察者模式:事件分发延迟的真相

观察者模式最容易被忽视的就是「通知风暴」。一个事件触发,成百上千的观察者同步执行,延迟会线性增长。

同步 vs 异步:延迟对比

我模拟了一个场景:1 个主题,N 个观察者,每个观察者处理耗时 1ms。结果如下:

观察者数量 同步分发(ms) 异步分发(ms) 说明
10 10.2 1.1 异步优势不明显
100 101.5 1.3 异步优势巨大
1000 1008.7 2.1 同步几乎不可用

为什么会这样?同步分发是串行的——一个观察者卡住,后面全得等。异步分发把任务丢到线程池,主线程立刻返回。

注意:异步不是银弹。线程池满了会丢任务,队列无限增长会 OOM。我曾经在一个支付系统中用无界队列做异步通知,结果高峰期内存直接爆了。后来改成有界队列 + 降级策略才稳住。

事件总线的优化思路

我参与过一个实时风控系统的架构设计,事件分发是核心瓶颈。最终我们用了三层优化:

  1. 事件分类:高优先级事件走同步,低优先级走异步
  2. 观察者分组:按业务域分组,每组独立线程池
  3. 背压机制:当队列积压超过阈值,直接丢弃低优先级事件

这套方案上线后,事件分发延迟从平均 200ms 降到了 5ms 以内。

知识体系总览

下面这张图总结了三种模式在性能维度的核心关注点:

三种模式性能优化核心要点 单例模式 • 饿汉式:最快,类加载即初始化 • 懒汉式:加锁开销大,慎用 • DCL:需 volatile 防重排 • 静态内部类:延迟加载+高性能 • 枚举:防反射/序列化 避坑: 不加 volatile 的 DCL synchronized 方法级别锁 工厂模式 • 简单工厂:if-else,性能最好 • 反射工厂:灵活但慢 30 倍 • 注册表工厂:Map 缓存 Class • 对象池:适合创建开销大的对象 避坑: 高频调用反射工厂 对象池管理有状态对象 观察者模式 • 同步分发:串行,延迟累加 • 异步分发:线程池,主线程快 • 事件分类:优先级调度 • 观察者分组:独立线程池 • 背压机制:有界队列+降级 避坑: 无界队列导致 OOM 观察者内做耗时操作 核心原则:先测试,后优化。不要过早优化,但也不要忽视性能瓶颈。

性能测试方法论

最后分享一点我个人的测试习惯。做模式性能测试时,别只看平均耗时,要关注 P99 和 P999。平均耗时可能被大量快速请求拉低,但长尾延迟才是真正的杀手。

举个例子,我测过一个观察者模式的异步分发,平均延迟只有 2ms,但 P999 达到了 800ms。一查发现是线程池满了,任务在队列里排队。后来加了监控告警,队列深度超过 100 就自动扩容线程池,P999 降到了 15ms。

总结一句话:单例看锁,工厂看反射,观察者看队列。性能优化没有银弹,但基准测试能帮你找到那根最长的木桶板。


公众号:蓝海资料掘金营,微信deep3321