什么是享元模式?
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。由此可以避免内存的占用以及创建对象的消耗。
优缺点
优点:
1.减少创建大量细粒度对象带来的性能消耗,以及内存的占用。
缺点:
1.需要和工厂模式搭配使用。
2.需要注意划分享元类内外状态,否则容易有线程安全问题。
内部状态指不随着环境,稳定的,可以被所有对象所共享的类成员变量。外部状态是指享元对象在被客户端使用时所传入的数据。
示例
享元模式被广泛应用于资源池类应用场景,JVM 在启动时会加载项目中所有的字面值常量到方法区,另外 Integer 类型(-128 ~ 127)和 Long 类型对应的值也是有被缓存的。在项目中,我们常用的数据库连接池是很好的例子,URL以及用户名密码都是不会发生变化的,因此是内部状态,而每个链接发送的请求就是外部状态。
我们来写一个简单的连接池,首先定义 Connection 和其工厂类,Connection 包含两个成员属性 name 和 client,在构造器中初始化,后续不许再改动。
class Connection {
// 内部状态,不可修改,不可有setter
private String name;
private HttpClient client;
public Connection(String name, HttpClient client) {
this.name = name;
this.client = client;
}
// request 为外部状态,随意客户端传入
public HttpResponse<String> send(HttpRequest request) throws IOException, InterruptedException {
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
public String getName() {
return name;
}
}
class ConnectionFactory {
private static HttpClient client = HttpClient.newBuilder().build();
public static Connection create() {
return new Connection("Lucien - " + System.currentTimeMillis(), client);
}
}
连接池对象,如果内置的 List 中还有可用 Connection 即直接返回,如果没有则创建新的。
class ConnectionPool {
private List<Connection> pool = new ArrayList<>();
public ConnectionPool(int initSize) {
for (int i = 0; i < initSize; i++) {
pool.add(ConnectionFactory.create());
}
}
public Connection get() {
if (!pool.isEmpty()) {
return this.pool.remove(0);
} else {
pool.add(ConnectionFactory.create());
return get();
}
}
}
测试代码
public static void main(String[] args) throws IOException, InterruptedException {
final HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://www.baidu.com")).GET().build();
// 为了方便验证,只初始化1个连接,第2个连接势必需要工厂类初始化新的连接
final ConnectionPool pool = new ConnectionPool(1);
final Connection connectionA = pool.get();
HttpResponse<String> responseA = connectionA.send(request);
System.out.println(connectionA.getName());
System.out.println(responseA.body());
Thread.sleep(1000);
final Connection connectionB = pool.get();
HttpResponse<String> responseB = connectionB.send(request);
System.out.println(connectionB.getName());
System.out.println(responseB.body());
}
Output
Lucien - 1676985868666
<!DOCTYPE html>
<!--STATUS OK--><html> <head>...
Lucien - 1676985868669
<!DOCTYPE html>
<!--STATUS OK--><html> <head>...