享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享已存在的对象来减少内存占用和对象创建开销,特别适合处理大量相似对象的场景。

核心思想是:将对象的属性分为内部状态(可共享,不随环境变化)和外部状态(不可共享,随环境变化),通过共享内部状态相同的对象,来减少系统中对象的数量。

享元模式的UML类图如下:

image.png
从图中可以看出

  • 享元工厂(FlyweightFactory):维护一个享元池(通常是哈希表),负责创建享元对象或复用已存在的对象。
  • 抽象享元(Flyweight):定义享元对象的接口,声明接收外部状态的方法。
  • 具体享元(ConcreteFlyweight):实现抽象享元接口,存储可共享的内部状态,方法参数接收外部状态。
  • 非享元(UnsharedConcreteFlyweight):不参与共享,通常包含不能共享的外部状态(可选角色)。
  • 客户端(Client):需要使用享元时,向工厂请求并提供外部状态,不直接创建享元对象。

我们来举一个例子。假设我们要实现一个数据库连接池。针对不同的数据库,其连接地址、用户名、密码等这些是变动的。但是,每个数据库连接都会有execute 执行SQL的方法和close关闭连接的方法,这两个方法是不变的。那么,我们来用享元模式实现它。
首先,将不变的部分封装成一个接口

1
2
3
4
5
6
7
// 抽象享元:数据库连接接口
interface DBConnection {
// 执行SQL(外部状态:SQL语句)
void execute(String sql);
// 释放连接(回归连接池)
void release();
}

然后将变动的部分封装在另一个部分,但是变动的部分会继承不变部分的功能。比如我们要实现一个MySQL的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 具体享元:数据库连接实现(内部状态为连接配置)
class MySQLConnection implements DBConnection {
// 内部状态:连接配置(可共享)
private String url;
private String username;
private String password;
// 外部状态标记:是否被占用(随使用变化)
private boolean inUse;

public MySQLConnection(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
this.inUse = false;
System.out.println("创建新连接:" + url);
}

@Override
public void execute(String sql) {
if (!inUse) {
throw new IllegalStateException("连接未被占用,无法执行SQL");
}
System.out.printf("连接[%s]执行SQL:%s%n", url, sql);
}

@Override
public void release() {
this.inUse = false;
System.out.printf("连接[%s]已释放,回归连接池%n", url);
}

// 标记连接为“被占用”
public void setInUse(boolean inUse) {
this.inUse = inUse;
}

public boolean isInUse() {
return inUse;
}
}

MySQLConnection是一个数据库连接,因此它继承了DBConnection接口,即需要实现execute和close方法。同时,mysql的连接url 、username、password等这些变动的部分在MySQLConnection中维护。

接下来,我们创建一个数据库连接池,它扮演了享元工厂的角色用于创建数据库连接并维护一个包含多条连接的连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 享元工厂:数据库连接池
class ConnectionPool {
// 享元池:存储可复用的连接
private List<MySQLConnection> connections = new ArrayList<>();
// 连接配置(内部状态模板)
private String url;
private String username;
private String password;
// 最大连接数
private int maxSize;

public ConnectionPool(String url, String username, String password, int maxSize) {
this.url = url;
this.username = username;
this.password = password;
this.maxSize = maxSize;
// 初始化最小连接数
for (int i = 0; i < Math.min(3, maxSize); i++) {
connections.add(new MySQLConnection(url, username, password));
}
}

// 获取连接(核心:复用空闲连接)
public DBConnection getConnection() {
// 1. 优先复用空闲连接
for (MySQLConnection conn : connections) {
if (!conn.isInUse()) {
conn.setInUse(true);
System.out.printf("复用连接:%s%n", url);
return conn;
}
}

// 2. 若未达最大连接数,创建新连接
if (connections.size() < maxSize) {
MySQLConnection newConn = new MySQLConnection(url, username, password);
newConn.setInUse(true);
connections.add(newConn);
return newConn;
}

// 3. 连接耗尽,等待(实际中会有超时机制)
System.out.println("连接池已满,等待空闲连接...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return getConnection(); // 递归重试
}
}

最后,在客户端调用时可以通过连接池对象创建数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 客户端:模拟多线程使用连接池
public class ConnectionPoolDemo {
public static void main(String[] args) {
// 创建连接池(享元工厂)
ConnectionPool pool = new ConnectionPool(
"jdbc:mysql://localhost:3306/mydb",
"root",
"123456",
5 // 最大连接数5
);

// 模拟10个线程并发获取连接
for (int i = 0; i < 10; i++) {
new Thread(() -> {
DBConnection conn = pool.getConnection();
// 执行SQL(外部状态)
conn.execute("SELECT * FROM users LIMIT 1");
// 释放连接(回归池)
conn.release();
}).start();
}
}
}

这里应用享元模式的价值在于:

  • 性能优化:避免频繁创建数据库连接(一次创建成本约 10-100ms),复用连接可提升系统吞吐量 10 倍以上。
  • 资源控制:通过最大连接数限制,防止数据库被连接耗尽。
  • 稳定性:连接池统一管理连接的创建、释放和异常处理,减少连接泄露风险。