享元模式:Flyweight Pattern,又叫作轻量级模式,是对象池的一种实现。类似线程池,线程池可以避免不停地创建和销毁多个对象,消耗性能。享元模式提供了减少对象数量从而改善应用所需的对象结构的方式
- 享元模式将一个对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是变化的;通过共享不变的部分,达到减少对象数量并节约内存的目的
- 内部状态指对象共享出来的信息,存储在享元对象内部,并且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享
- 享元模式的本质是共享细粒度对象,将多个对同一对象的访问集中起来,不必为每个访问者都创建一个单独的对象,以此来降低内存的消耗
主要应用场景:
- 系统底层的开发,解决性能问题
- 系统中有大量相似对象,需要缓冲池的场景
享元模式通用模板
享元模式的 UML 图:
享元模式的角色:
- 抽象享元角色:Flyweight,享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或实现
- 具体享元角色:ConcreteFlyweight,实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不会出现一个操作改变内部状态、同时修改了外部状态的情况
享元工厂:FlyweightFactory,负责管理享元对象池和创建享元对象
public class FlyweightDemo {
public static void main(String[] args) {
Flyweight flyweight = FlyweightFactory.getFlyweight("世界");
flyweight.operation("你好");
}
interface Flyweight {
void operation(String extrinsicState);
}
// 具体享元角色
static class ConcreteFlyweight implements Flyweight {
// 内部状态,不变的对象
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("内部不变对象:"+extrinsicState);
}
}
// 享元工厂
static class FlyweightFactory {
private static Map<String, Flyweight> pool = new HashMap<>();
public static Flyweight getFlyweight(String intrinsicState) {
// 由于内部状态具有不变性,所以作为缓存的键
if (!pool.containsKey(intrinsicState)) {
Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
pool.put(intrinsicState, flyweight);
}
return pool.get(intrinsicState);
}
}
}
享元模式实现连接池
利用享元模式实现一个连接池:
public class DataSourcePool {
private List<Connection> pool;
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "1234";
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private final int poolSize = 20;
public DataSourcePool() {
pool = new LinkedList<Connection>();
try {
Class.forName(driverClassName);
for (int i = 0; i < poolSize; i++) {
Connection connection = DriverManager.getConnection(url, username, password);
pool.add(connection);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized Connection getConnection(){
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
}
return null;
}
public synchronized void release(Connection connection) {
pool.add(connection);
}
}
享元模式在 jdk 中的应用
String:由 final 修饰,属于不可变对象,一般保存在字符常量池中,java 会确保一个串在字符常量池中只有一个复制
- 字符串常量池的位置:
- jdk 1.7 之前:位于常量池中,属于永久代
- jdk 1.8 之后:放在堆中
字符常量池的特点:
- 字符串常量池中的字面量是 JVM 在编译期间就已经创建好,并在 java 启动时就已经加载到内存中
- 在通过字面量创建 String 对象时,如果常量池存在相同的字面量,则返回这个字面量的引用;如果不存在相同的字面量,则在字符串常量池中创建这个字面量并返回它的引用
有且只有一份相同的字面量
public static void main(String[] args) {
// JVM在编译期间就将"Hello"字面量放到字符串常量池
// 在java启动的时候就已经加载到内存中,所以s1执行的是常量池中的引用
String s1 = "Hello";
// s2指向的字面量在常量池中已经存在,返回字面量的引用
String s2 = "Hello";
// 字面量的拼接,JVM在编译期间会进行优化,所以
String s3 = "He" + "llo";
// new String("lo") 生成了两个对象:字符串常量池中的字面量"lo" 和 new String("lo") 存在于堆中
// String s4 = "hel" + new String("lo") 实质上是两个对象的相加,编译器不会进行优化,相加的结果存在于堆中,而s1存在于字符串常量池中,当然不相等
String s4 = "hel" + new String("lo");
// 由于字面量 Hello 已经存在字符串常量池,因此new String("Hello")只在堆中创建了一个对象
String s5 = new String("Hello");
// s5.intern()一个位于堆中的字符串在运行期间动态地加入字符串常量池(字符串常量池的内容是在程序启动的时候就已经加载好了的)
// 如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用;否则,复制一份该字面量到字符串常量池并返回它的引用
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
// 是指是两个对象的相加,编译器不会优化,相加的结果位于堆中
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s6); // true
System.out.println(s1 == s9); // false
}
享元模式在 Apache Pool 源码中的应用
对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象造成的消耗。用于充当保存对象的“容器”的对象,被称为对象池(Object Pool,简称Pool)
Apache Pool实现了对象池的功能,定义了对象的生成、销毁、激活、钝化等操作及其状态转换,并提供几个默认的对象池实现,有如下几个重要的角色。
- Pooled Object(池化对象):用于封装对象(例如,线程、数据库连接和TCP连接),将其包裹成可被对象池管理的对象
- Pooled Object Factory(池化对象工厂):定义了操作PooledObject实例生命周期的一些方法,Pooled Object Factory必须实现线程安全
- Object Pool(对象池):Object Pool负责管理PooledObject,例如,借出对象、返回对象、校验对象、有多少激活对象和有多少空闲对象