享元模式: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;}@Overridepublic 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); // trueSystem.out.println(s1 == s3); // trueSystem.out.println(s1 == s4); // falseSystem.out.println(s1 == s6); // trueSystem.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,例如,借出对象、返回对象、校验对象、有多少激活对象和有多少空闲对象
