当系统中存在大量相似对象时,每个对象都需要占用一定的内存空间,如果这些对象的大部分属性是相同的,那么频繁创建这些对象会导致内存消耗过大。享元模式将这些相同部分抽取出来作为共享的内部状态,在需要时进行共享,从而减少内存占用。
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来最大化内存利用和性能提升,享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
享元模式在对象池中的使用是一种常见的场景,通过对象池管理和复用对象实例,可以提高系统性能和资源利用率。对象池通常用于缓存、连接池等场景,其中对象的创建成本较高或者频繁创建销毁会影响性能时,对象池就显得尤为重要。
在 Java 中,String 类的 intern() 方法是享元模式的一个应用。intern() 方法返回字符串对象的规范化表示形式,即返回字符串池中与调用字符串等效的字符串。如果字符串池中已经存在等效的字符串,则返回该字符串;否则,将此字符串添加到字符串池中,并返回新的字符串引用。
下面是一个示例代码,演示了 String 类的 intern() 方法的应用:
public class StringInternExample { public static void main(String[] args) { String str1 = "hello"; String str2 = new String("hello"); String str3 = str2.intern(); System.out.println("str1 == str2: " + (str1 == str2)); // false System.out.println("str1 == str3: " + (str1 == str3)); // true }}
在上述示例中,str1 和 str2 是两个不同的字符串对象,尽管它们的值相同,但由于 str2 使用了 new String() 构造方法创建,在堆内存中会生成一个新的对象。而通过调用 intern() 方法后,str3 返回的是字符串池中已存在的字符串对象,因此 str1 和 str3 指向的是同一个对象,所以输出结果为 "str1 == str3: true"。这就是 intern() 方法的享元模式应用,避免了重复创建相同的字符串对象,节省了内存空间。
享元模式包含以下几个角色:
在享元模式中,核心在于区分内部状态和外部状态。内部状态是可以共享的部分,而外部状态是对象的非共享部分。
通过区分内部状态和外部状态,享元模式实现了将对象的共享部分和变化部分分离的目的,有效地减少了系统中重复对象的数量,提高了系统的性能和资源利用率。内部状态是享元对象本身的属性,而外部状态则是根据具体情况动态变化的参数。
实现步骤和示例代码如下:
1.首先定义抽象享元角色。
public abstract class Flyweight { //内部状态 private String intrinsic; //外部状态 protected final String extrinsic; //要求享元角色必须接受外部状态 public Flyweight(String extrinsic){ this.extrinsic = extrinsic; } //定义业务操作 public abstract void operate(); //内部状态的getter/setter public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; }}
抽象享元角色一般为抽象类,它是描述一类事物的方法。
2.具体享元角色。
public class ConcreteFlyweight1 extends Flyweight{ //接受外部状态 public ConcreteFlyweight1(String extrinsic){ super(extrinsic); } //根据外部状态进行逻辑处理 public void operate(){ //业务逻辑 }}
public class ConcreteFlyweight2 extends Flyweight{ //接受外部状态 public ConcreteFlyweight2(String extrinsic){ super(extrinsic); } //根据外部状态进行逻辑处理 public void operate(){ //业务逻辑 }}
具体享元角色实现自己的业务逻辑,然后接收外部状态,以便内部业务逻辑对外部状态的依赖。
3.享元工厂。
public class FlyweightFactory { //定义一个池容器 private static Map<String, Flyweight> pool = new HashMap<>(); //享元工厂 public static Flyweight getFlyweight(String extrinsic) { //需要返回的对象 Flyweight flyweight; //在池中没有该对象 if (pool.containsKey(extrinsic)) { flyweight = pool.get(extrinsic); } else { //根据外部状态创建享元对象 flyweight = new ConcreteFlyweight1(extrinsic); //放置到池中 pool.put(extrinsic, flyweight); } return flyweight; }}
4.客户端调用
public static void main(String[] args) { Flyweight flyweight1 = FlyweightFactory.getFlyweight("hello world"); System.out.println(flyweight1.hashCode()); Flyweight flyweight2 = FlyweightFactory.getFlyweight("hello world"); System.out.println(flyweight2.hashCode()); } Output: 1705736037 1705736037
可以发现对象打印的 hashCode 一致,说明对象得到了复用。
Tips:外部状态最好以Java的基本类型作为标志,如String、int等,可以大幅地提升效率。如果使用自己编写的类作为外部状态,则必须覆写equals方法和hashCode方法,否则会出现通过键值搜索失败的情况,例如map.get(object)、map.contains(object)等会返回失败的结果。
享元模式在多线程环境下可能存在线程安全问题,主要原因是享元对象的内部状态和外部状态被多个线程共享和修改,可能导致数据竞争和不一致性。具体来说,如果多个线程同时尝试修改同一个享元对象的外部状态,就会引发线程安全问题。
下面是示例代码:
public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { Flyweight flyweight1 = FlyweightFactory.getFlyweight("hello world"); Flyweight flyweight2 = FlyweightFactory.getFlyweight("hello world"); System.out.println(flyweight1 == flyweight2); }).start(); } }Output:truefalsetruetruetruetruetruetruetruetrue
这段代码展示了多线程环境下使用享元模式的示例。在 main 方法中,通过循环创建了 10 个线程,在每个线程中尝试获取表示 "hello world" 的享元对象,并比较两个获取的对象是否相等。
可以观察到输出中存在 false,说明对象不一样了,存在线程安全问题。
要想实现线程安全,需要对享元工厂类稍加改造,代码如下:
public class FlyweightFactory { //定义一个池容器 private static Map<String, Flyweight> pool = new ConcurrentHashMap<>(); //享元工厂 public static synchronized Flyweight getFlyweight(String extrinsic) { Flyweight flyweight = pool.putIfAbsent(extrinsic, new ConcreteFlyweight1(extrinsic)); if (flyweight == null) { return pool.get(extrinsic); } return flyweight; }}
这样就解决了线程安全问题,不过性能上会有所降低,在需要的地方考虑一下线程安全即可,在大部分的场景下都不用考虑。
享元模式通过共享相似对象来减少内存消耗,提高系统性能。它适用于存在大量相似对象且造成内存浪费的场景,但需要注意对内部状态和外部状态的管理。合理应用享元模式可以有效优化系统架构,提升性能。
优点
缺点
本文链接:http://www.28at.com/showinfo-26-75345-0.html一文搞懂设计模式—享元模式
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。