享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享已存在的对象来减少内存占用和对象创建开销,特别适合处理大量相似对象的场景。
核心思想是:将对象的属性分为内部状态(可共享,不随环境变化)和外部状态(不可共享,随环境变化),通过共享内部状态相同的对象,来减少系统中对象的数量。
享元模式的UML类图如下:

从图中可以看出
- 享元工厂(FlyweightFactory):维护一个享元池(通常是哈希表),负责创建享元对象或复用已存在的对象。
- 抽象享元(Flyweight):定义享元对象的接口,声明接收外部状态的方法。
- 具体享元(ConcreteFlyweight):实现抽象享元接口,存储可共享的内部状态,方法参数接收外部状态。
- 非享元(UnsharedConcreteFlyweight):不参与共享,通常包含不能共享的外部状态(可选角色)。
- 客户端(Client):需要使用享元时,向工厂请求并提供外部状态,不直接创建享元对象。
我们来举一个例子。假设我们要实现一个数据库连接池。针对不同的数据库,其连接地址、用户名、密码等这些是变动的。但是,每个数据库连接都会有execute 执行SQL的方法和close关闭连接的方法,这两个方法是不变的。那么,我们来用享元模式实现它。
首先,将不变的部分封装成一个接口
1 2 3 4 5 6 7
| interface DBConnection { 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() { for (MySQLConnection conn : connections) { if (!conn.isInUse()) { conn.setInUse(true); System.out.printf("复用连接:%s%n", url); return conn; } }
if (connections.size() < maxSize) { MySQLConnection newConn = new MySQLConnection(url, username, password); newConn.setInUse(true); connections.add(newConn); return newConn; }
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 );
for (int i = 0; i < 10; i++) { new Thread(() -> { DBConnection conn = pool.getConnection(); conn.execute("SELECT * FROM users LIMIT 1"); conn.release(); }).start(); } } }
|
这里应用享元模式的价值在于:
- 性能优化:避免频繁创建数据库连接(一次创建成本约 10-100ms),复用连接可提升系统吞吐量 10 倍以上。
- 资源控制:通过最大连接数限制,防止数据库被连接耗尽。
- 稳定性:连接池统一管理连接的创建、释放和异常处理,减少连接泄露风险。