一、享元模式:对象复用,线程池与连接池中的享元模式
享元模式,说白了就是「共享对象」。
我刚开始接触这个模式时,觉得它挺简单的——不就是复用对象嘛。但真正在项目中用起来,才发现里面的门道不少。你想想看,如果一个系统里创建了成千上万个相同的对象,内存迟早会爆。享元模式就是来解决这个问题的。
1.1 什么是享元模式
享元模式的核心思想是:通过共享技术,减少大量细粒度对象的创建。
它把对象的状态分为两类:
- 内部状态:可共享的、不变的部分。比如字符的字体、颜色。
- 外部状态:不可共享的、随上下文变化的部分。比如字符的位置、大小。
嗯,这里要注意:内部状态必须是不变的。如果变了,共享就出问题了。
核心要点:享元模式通过「共享内部状态 + 传入外部状态」来减少对象数量。
1.2 一个简单的例子
我记得有一次做文本编辑器,需要显示大量字符。如果每个字符都创建一个对象,那内存根本扛不住。用享元模式就解决了。
// 享元接口
public interface Character {
void display(int fontSize, String color);
}
// 具体享元类
public class ConcreteCharacter implements Character {
private final char symbol; // 内部状态,不可变
public ConcreteCharacter(char symbol) {
this.symbol = symbol;
}
@Override
public void display(int fontSize, String color) {
System.out.println("字符: " + symbol +
", 字号: " + fontSize +
", 颜色: " + color);
}
}
// 享元工厂
public class CharacterFactory {
private final Map<Character, ConcreteCharacter> pool = new HashMap<>();
public ConcreteCharacter getCharacter(char key) {
ConcreteCharacter character = pool.get(key);
if (character == null) {
character = new ConcreteCharacter(key);
pool.put(key, character);
System.out.println("创建新字符: " + key);
}
return character;
}
}
你看,同一个字符 'A' 只会被创建一次。外部状态(字号、颜色)每次传入即可。
个人经验:我在项目中习惯用 HashMap 做享元池,但要注意线程安全。如果并发高,建议用 ConcurrentHashMap。
1.3 享元模式的结构
享元模式的结构其实很清晰:
- Flyweight(享元接口):定义对外接口,接收外部状态。
- ConcreteFlyweight(具体享元):实现享元接口,存储内部状态。
- FlyweightFactory(享元工厂):创建和管理享元对象。
- Client(客户端):维护外部状态,调用享元对象。
1.4 线程池中的享元模式
线程池是享元模式最经典的应用之一。我做过一个高并发项目,如果不复用线程,每来一个请求就 new 一个线程,系统很快就挂了。
ExecutorService executor = Executors.newFixedThreadPool(10);
// 这10个线程会被反复使用,而不是每次都创建新线程
线程池的工作原理:
- 内部维护一个线程集合(享元池)
- 任务提交时,从池中取一个空闲线程执行
- 执行完毕后,线程不销毁,而是回到池中等待下一个任务
避坑指南:我曾经在项目中把线程池设得太大,结果内存溢出。后来我习惯根据 CPU 核心数和任务类型来估算线程数。一般 IO 密集型任务可以多设一些,CPU 密集型任务不宜超过核心数。
1.5 连接池中的享元模式
数据库连接池也是享元模式的典型应用。每次操作数据库都创建连接,那性能太差了。
// 以 HikariCP 为例
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
HikariDataSource dataSource = new HikariDataSource(config);
// 从池中获取连接
Connection conn = dataSource.getConnection();
// 使用连接...
conn.close(); // 这里不是真正关闭,而是归还到池中
连接池的好处:
- 避免频繁创建/销毁连接的开销
- 控制并发连接数,防止数据库被打爆
- 提供连接超时、重连等机制
个人经验:我建议连接池的大小不要设置成固定的,可以根据实际负载动态调整。比如用 HikariCP 的 minimumIdle 和 maximumPoolSize 配合使用。
1.6 享元模式的优缺点
| 优点 | 缺点 |
|---|---|
| 减少对象数量,节省内存 | 需要分离内部状态和外部状态,增加复杂度 |
| 提高系统性能 | 如果外部状态变化频繁,反而降低性能 |
| 便于管理共享资源 | 享元工厂可能成为瓶颈 |
1.7 什么时候用享元模式
我个人总结了几条经验:
- 系统中有大量相似对象,且内存消耗严重
- 对象的大部分状态可以外部化
- 需要复用对象,且对象创建成本高
- 需要控制资源数量(如线程、连接)
核心总结:享元模式不是万能的。如果对象状态变化频繁,或者共享带来的收益不大,就别硬用。我见过有人为了用设计模式而用设计模式,结果代码变得很复杂,性能反而下降了。
嗯,享元模式就讲到这里。记住一句话:能复用就别创建,能共享就别独享。线程池、连接池都是这个道理。
公众号:蓝海资料掘金营,微信deep3321