Java性能优化设计模式篇
Java性能优化 · 设计模式篇
其实设计模式本身不直接提升性能,它是代码组织结构的最佳实践。当一种性能优化的实现途径恰好契合某种模式时,模式就成了这种优化的标准表达。
性能优化的目的是「让有限的资源(CPU/内存/线程/连接)服务更多业务请求」,模式只是把这种优化沉淀为可复用、可读的设计。
单例模式
单例模式解决的就是创建单一对象优化系统性能。一个类在系统中可能被多处频繁使用,如果每次都 new,会浪费资源;尤其在并发场景下,更需要保证「全局只有一份」。
单例模式的三要点:只能有一个实例;自行创建;自行向整个系统提供。
现在的主要方式就两种:
- 双重检测 + volatile
- 静态内部类(推荐)
静态内部类骨架:
1 | |
外部类加载时不会加载内部类,只有第一次调用 getInstance() 才会触发内部类加载与实例化,懒加载 + 线程安全 + 高性能全都要。
原型模式与享元模式
原型模式:加速对象创建,解决「创建过程太重」。
享元模式:减少对象数量,解决「对象总数太多」。
原型模式(Prototype)
核心问题:构造函数做了大量初始化(大数组、复杂计算),循环中反复 new 浪费 CPU。
关键代码骨架:
1 | |
Object.clone()是 native 方法,直接操作内存二进制流,比new少了「调用构造函数」这一步。- 浅拷贝:
super.clone()默认只复制基本类型与引用,引用类型成员仍指向同一对象。 - 深拷贝:在
clone()内对引用类型成员递归调用其clone()。
典型应用:Spring 中 @Scope("prototype") 的 bean,避免单例 + 全局变量被多次注入时被相互污染。
享元模式(Flyweight)
核心问题:大量对象其实共享同一份内部数据,反复创建浪费内存。
核心思想:把对象状态拆成内部状态(可共享、不随运行改变)和外部状态(运行时不同值),内部状态相同的对象共用一份。
典型例子:
String常量池:String s1 = "hi"; String s2 = "hi"; s1 == s2为true。- 线程池:线程不是每次都重新创建,而是优先复用线程池中已经存在的线程。
对比与选型
| 维度 | 原型模式 | 享元模式 |
|---|---|---|
| 目的 | 加速单次对象创建过程 | 减少对象总数,共享内部状态 |
| 手段 | clone() 替代 new |
工厂 + 池化复用已有对象 |
| 适用场景 | 需要重复创建不同实例 | 可以共用同一内部数据的对象 |
| 内存占用 | 多个独立对象 | 单个共享对象 + 外部状态 |
选型决策:是否需要重复创建不同实例?是 → 原型。是否能共用同一内部数据?是 → 享元。
注意浅拷贝:引用类型成员只复制引用,修改克隆体对象会导致原对象也被修改!
观察者模式
在一些业务场景中,除关键流程外可能还有非关键流程。
一个常见的例子:用户注册账号后向其邮箱里发送一份欢迎邮件,但发送欢迎邮件并不是注册账号的核心流程。如果在注册账号的流程里完成发送邮件的操作,一方面功能没有那么单一了,另一方面会拖慢接口的响应时间。
一个简单的方案是:在注册完成后写消息到消息队列里,流量缓冲+业务解耦,这种方式偏向于“生产者消费者”模式。还有一种方式是在注册完成后发布事件,观察者观察到了这个事件,接着触发对应的处理,如 SpringBoot 里的 EventListener。
骨架代码:
1 | |
要点:
- 类型驱动:
Map<Class<?>, List<EventListener>>按事件类型归类观察者,避免遍历所有监听器做instanceof判断。 - 线程安全:用
ConcurrentHashMap+CopyOnWriteArrayList,注册/发布互不阻塞;CopyOnWriteArrayList的写时复制适合「读多写少」的监听器列表。 - 性能关键点:
publish()里的for循环若同步执行会拖慢主流程,实际项目应丢到独立线程池异步执行(或像 Spring 的@Async/@EventListener配合@EnableAsync);若观察者较多且允许乱序,可用parallelStream()并行触发。 - vs 生产者消费者:观察者模式是「进程内同步/异步回调」,MQ 是「跨进程削峰」,前者更轻,后者更稳;能容忍丢失就先用观察者,要严格可靠才上 MQ。
面向对象设计模式里有几个不太常用,剩下的都有使用的场景,如装饰器模式实现功能增强;策略模式解决 if else 多判断;责任链模式实现流式处理,新增处理只需要新增节点等。