當(dāng)系統(tǒng)中存在大量相似對(duì)象時(shí),每個(gè)對(duì)象都需要占用一定的內(nèi)存空間,如果這些對(duì)象的大部分屬性是相同的,那么頻繁創(chuàng)建這些對(duì)象會(huì)導(dǎo)致內(nèi)存消耗過(guò)大。享元模式將這些相同部分抽取出來(lái)作為共享的內(nèi)部狀態(tài),在需要時(shí)進(jìn)行共享,從而減少內(nèi)存占用。
享元模式(Flyweight Pattern)是一種結(jié)構(gòu)型設(shè)計(jì)模式,旨在通過(guò)共享對(duì)象來(lái)最大化內(nèi)存利用和性能提升,享元模式嘗試重用現(xiàn)有的同類對(duì)象,如果未找到匹配的對(duì)象,則創(chuàng)建新對(duì)象。
享元模式在對(duì)象池中的使用是一種常見(jiàn)的場(chǎng)景,通過(guò)對(duì)象池管理和復(fù)用對(duì)象實(shí)例,可以提高系統(tǒng)性能和資源利用率。對(duì)象池通常用于緩存、連接池等場(chǎng)景,其中對(duì)象的創(chuàng)建成本較高或者頻繁創(chuàng)建銷毀會(huì)影響性能時(shí),對(duì)象池就顯得尤為重要。
在 Java 中,String 類的 intern() 方法是享元模式的一個(gè)應(yīng)用。intern() 方法返回字符串對(duì)象的規(guī)范化表示形式,即返回字符串池中與調(diào)用字符串等效的字符串。如果字符串池中已經(jīng)存在等效的字符串,則返回該字符串;否則,將此字符串添加到字符串池中,并返回新的字符串引用。
下面是一個(gè)示例代碼,演示了 String 類的 intern() 方法的應(yīng)用:
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 是兩個(gè)不同的字符串對(duì)象,盡管它們的值相同,但由于 str2 使用了 new String() 構(gòu)造方法創(chuàng)建,在堆內(nèi)存中會(huì)生成一個(gè)新的對(duì)象。而通過(guò)調(diào)用 intern() 方法后,str3 返回的是字符串池中已存在的字符串對(duì)象,因此 str1 和 str3 指向的是同一個(gè)對(duì)象,所以輸出結(jié)果為 "str1 == str3: true"。這就是 intern() 方法的享元模式應(yīng)用,避免了重復(fù)創(chuàng)建相同的字符串對(duì)象,節(jié)省了內(nèi)存空間。
享元模式包含以下幾個(gè)角色:
在享元模式中,核心在于區(qū)分內(nèi)部狀態(tài)和外部狀態(tài)。內(nèi)部狀態(tài)是可以共享的部分,而外部狀態(tài)是對(duì)象的非共享部分。
通過(guò)區(qū)分內(nèi)部狀態(tài)和外部狀態(tài),享元模式實(shí)現(xiàn)了將對(duì)象的共享部分和變化部分分離的目的,有效地減少了系統(tǒng)中重復(fù)對(duì)象的數(shù)量,提高了系統(tǒng)的性能和資源利用率。內(nèi)部狀態(tài)是享元對(duì)象本身的屬性,而外部狀態(tài)則是根據(jù)具體情況動(dòng)態(tài)變化的參數(shù)。
實(shí)現(xiàn)步驟和示例代碼如下:
1.首先定義抽象享元角色。
public abstract class Flyweight { //內(nèi)部狀態(tài) private String intrinsic; //外部狀態(tài) protected final String extrinsic; //要求享元角色必須接受外部狀態(tài) public Flyweight(String extrinsic){ this.extrinsic = extrinsic; } //定義業(yè)務(wù)操作 public abstract void operate(); //內(nèi)部狀態(tài)的getter/setter public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; }}
抽象享元角色一般為抽象類,它是描述一類事物的方法。
2.具體享元角色。
public class ConcreteFlyweight1 extends Flyweight{ //接受外部狀態(tài) public ConcreteFlyweight1(String extrinsic){ super(extrinsic); } //根據(jù)外部狀態(tài)進(jìn)行邏輯處理 public void operate(){ //業(yè)務(wù)邏輯 }}
public class ConcreteFlyweight2 extends Flyweight{ //接受外部狀態(tài) public ConcreteFlyweight2(String extrinsic){ super(extrinsic); } //根據(jù)外部狀態(tài)進(jìn)行邏輯處理 public void operate(){ //業(yè)務(wù)邏輯 }}
具體享元角色實(shí)現(xiàn)自己的業(yè)務(wù)邏輯,然后接收外部狀態(tài),以便內(nèi)部業(yè)務(wù)邏輯對(duì)外部狀態(tài)的依賴。
3.享元工廠。
public class FlyweightFactory { //定義一個(gè)池容器 private static Map<String, Flyweight> pool = new HashMap<>(); //享元工廠 public static Flyweight getFlyweight(String extrinsic) { //需要返回的對(duì)象 Flyweight flyweight; //在池中沒(méi)有該對(duì)象 if (pool.containsKey(extrinsic)) { flyweight = pool.get(extrinsic); } else { //根據(jù)外部狀態(tài)創(chuàng)建享元對(duì)象 flyweight = new ConcreteFlyweight1(extrinsic); //放置到池中 pool.put(extrinsic, flyweight); } return flyweight; }}
4.客戶端調(diào)用
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
可以發(fā)現(xiàn)對(duì)象打印的 hashCode 一致,說(shuō)明對(duì)象得到了復(fù)用。
Tips:外部狀態(tài)最好以Java的基本類型作為標(biāo)志,如String、int等,可以大幅地提升效率。如果使用自己編寫的類作為外部狀態(tài),則必須覆寫equals方法和hashCode方法,否則會(huì)出現(xiàn)通過(guò)鍵值搜索失敗的情況,例如map.get(object)、map.contains(object)等會(huì)返回失敗的結(jié)果。
享元模式在多線程環(huán)境下可能存在線程安全問(wèn)題,主要原因是享元對(duì)象的內(nèi)部狀態(tài)和外部狀態(tài)被多個(gè)線程共享和修改,可能導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)和不一致性。具體來(lái)說(shuō),如果多個(gè)線程同時(shí)嘗試修改同一個(gè)享元對(duì)象的外部狀態(tài),就會(huì)引發(fā)線程安全問(wèn)題。
下面是示例代碼:
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
這段代碼展示了多線程環(huán)境下使用享元模式的示例。在 main 方法中,通過(guò)循環(huán)創(chuàng)建了 10 個(gè)線程,在每個(gè)線程中嘗試獲取表示 "hello world" 的享元對(duì)象,并比較兩個(gè)獲取的對(duì)象是否相等。
可以觀察到輸出中存在 false,說(shuō)明對(duì)象不一樣了,存在線程安全問(wèn)題。
要想實(shí)現(xiàn)線程安全,需要對(duì)享元工廠類稍加改造,代碼如下:
public class FlyweightFactory { //定義一個(gè)池容器 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; }}
這樣就解決了線程安全問(wèn)題,不過(guò)性能上會(huì)有所降低,在需要的地方考慮一下線程安全即可,在大部分的場(chǎng)景下都不用考慮。
享元模式通過(guò)共享相似對(duì)象來(lái)減少內(nèi)存消耗,提高系統(tǒng)性能。它適用于存在大量相似對(duì)象且造成內(nèi)存浪費(fèi)的場(chǎng)景,但需要注意對(duì)內(nèi)部狀態(tài)和外部狀態(tài)的管理。合理應(yīng)用享元模式可以有效優(yōu)化系統(tǒng)架構(gòu),提升性能。
優(yōu)點(diǎn)
缺點(diǎn)
本文鏈接:http://www.www897cc.com/showinfo-26-75345-0.html一文搞懂設(shè)計(jì)模式—享元模式
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com