享元设计模式
概述
享元设计模式(Flyweight Design Pattern)是一种用于性能优化的设计模式,它通过共享尽可能多的相似对象来减少对象的创建,从而降低内存使用和提高性能。享元模式的核心思想是将对象的共享部分提取出来,使得多个对象可以共享同一个享元对象,从而减少对象的创建。
定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
结构
享元(Flyweight )模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
享元模式的结构如下:
-
抽象享元类(Flyweight):定义一个抽象类,包含一个享元对象的属性以及一个构造函数。
-
具体享元类(ConcreteFlyweight):实现抽象享元类,定义具体的享元对象。
-
享元工厂类(FlyweightFactory):负责创建和管理享元对象。享元工厂类会缓存已经创建的享元对象,当需要获取享元对象时,首先从缓存中查找,如果找不到,则创建一个新的享元对象并将其添加到缓存中。
享元模式的实现示例:
以下是一个简单的享元模式实现示例:
// 抽象享元类
public abstract class Flyweight {public abstract void operation();
}// 具体享元类
public class ConcreteFlyweight extends Flyweight {private String state;public ConcreteFlyweight(String state) {this.state = state;}@Overridepublic void operation() {System.out.println("ConcreteFlyweight: " + state);}
}// 享元工厂类
public class FlyweightFactory {private Map<String, Flyweight> flyweights = new HashMap<>();public Flyweight getFlyweight(String state) {if (!flyweights.containsKey(state)) {flyweights.put(state, new ConcreteFlyweight(state));}return flyweights.get(state);}
}// 客户端
public class Client {public static void main(String[] args) {FlyweightFactory factory = new FlyweightFactory();Flyweight flyweight1 = factory.getFlyweight("state1");Flyweight flyweight2 = factory.getFlyweight("state2");Flyweight flyweight3 = factory.getFlyweight("state1");flyweight1.operation();flyweight2.operation();flyweight3.operation();}
}
在这个示例中,我们定义了一个抽象享元类Flyweight
和一个具体享元类ConcreteFlyweight
。FlyweightFactory
类负责创建和管理享元对象,它使用一个HashMap
来缓存已经创建的享元对象。客户端通过FlyweightFactory
类来获取享元对象,并使用它们进行操作。
示例二:
/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 享元设计模式 - 抽象享元类*/
public abstract class AbstractBox {public abstract String getSharp();public void display(String color) {System.out.println("形状:" + this.getSharp() + ",颜色:" + color);}
}/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 具体享元类 - I图形*/
public class IBox extends AbstractBox {@Overridepublic String getSharp() {return "I";}
}/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 具体享元类 - L图形*/
public class LBox extends AbstractBox {@Overridepublic String getSharp() {return "L";}
}/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 具体享元类 - O图形*/
public class OBox extends AbstractBox {@Overridepublic String getSharp() {return "O";}
}
/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 图形工厂类 - 享元工厂类 【单例实现】*/
public class BoxFactory {private final Map<String, AbstractBox> map;private static final BoxFactory factory = new BoxFactory();private BoxFactory() {map = new HashMap<>();map.put("I", new IBox());map.put("O", new OBox());map.put("L", new LBox());}public static BoxFactory getInstance() {return factory;}public AbstractBox createBox(String name) {return map.get(name);}
}
/*** @author OldGj 2024/02/29* @version v1.0* @apiNote 客户端 - 测试类*/
public class Client {public static void main(String[] args) {BoxFactory factory = BoxFactory.getInstance();AbstractBox LBox = factory.createBox("L");LBox.display("红色"); // 外部状态AbstractBox oBox = factory.createBox("O");oBox.display("黑色"); // 外部状态AbstractBox IBox = factory.createBox("I");IBox.display("白色"); // 外部状态AbstractBox IBox2 = factory.createBox("I");IBox2.display("蓝色"); // 外部状态System.out.println("是否共享同一个对象:" + (IBox == IBox2));}
}
优缺点:
享元模式的优点:
-
减少对象创建:享元模式通过共享相似对象,减少了对象的创建,从而降低了内存使用和提高了性能。
-
提高性能:享元模式通过减少对象创建,提高了性能。
-
方便管理:享元模式将对象的共享部分提取出来,使得对象可以轻松地管理和维护。
享元模式的缺点:
-
享元对象共享:享元对象共享可能会导致一些问题,例如线程安全问题。享元模式要求享元对象是线程安全的,这可能会导致性能下降。
-
享元对象状态:享元对象的状态可能会影响其他使用相同享元对象的客户端。享元模式要求享元对象的状态是独立的,这可能会导致享元对象的状态难以维护。
-
享元对象生命周期:享元对象的生命周期可能会比客户端使用的时间长,这可能会导致内存泄漏。享元模式要求享元对象具有较长的生命周期,这可能会导致内存使用增加。
使用场景:
享元模式通常用于以下场景:
-
对象数量多且相似:当对象数量多且相似时,享元模式可以显著减少对象的创建,提高性能。
-
对象共享属性多:当对象共享属性多时,享元模式可以减少内存使用,提高性能。
-
对象创建开销大:当对象创建开销大时,享元模式可以减少对象创建的开销,提高性能。
JDK源码解析
Integer类使用了享元模式。我们先看下面的例子:
public class Demo {public static void main(String[] args) {Integer i1 = 127;Integer i2 = 127;System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));Integer i3 = 128;Integer i4 = 128;System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));}
}
运行上面代码,结果如下:
为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:
public class Demo {public static void main(String[] args) {Integer i1 = Integer.valueOf((int)127);Integer i2 Integer.valueOf((int)127);System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());Integer i3 = Integer.valueOf((int)128);Integer i4 = Integer.valueOf((int)128);System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());}
}
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf()
,所以只需要看该方法即可
public final class Integer extends Number implements Comparable<Integer> {public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {int h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}
}
可以看到 Integer
默认先创建并缓存 -128 ~ 127
之间数的 Integer
对象,当调用 valueOf
时如果参数在 -128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的 Integer
对象。