28.4 享元模式的擴展
28.4.1 線程安全的問題
線程安全是一個老生常談的話題,只要使用Java開發都會遇到這個問題,我們之所以要在今天的享元模式中提到該問題,是因為該模式有太大的幾率發生線程不安全,為什么呢?
我們還以報考系統為例來說明這個問題。大家有沒有想過,為什么要以考試科目+考試地點作為外部狀態呢?為什么不能以考試科目或者考試地點作為外部狀態呢?這樣池中的對象會更少!可以!完全可以!我們把程序以考試科目為外部狀態,把享元工廠稍作修改,如代碼清單28-10所示。
代碼清單28-10 報考信息工廠
public?class?SignInfoFactory?{
?????//池容器
?????private?static?HashMap<String,SignInfo>?pool?=?new?HashMap<String,SignInfo>();
?????//從池中獲得對象
?????public?static?SignInfo?getSignInfo(String?key){
?????????????//設置返回對象
?????????????SignInfo?result?=?null;
?????????????//池中沒有該對象,則建立,并放入池中
?????????????if(!pool.containsKey(key)){
?????????????????????result?=?new?SignInfo();
?????????????????????pool.put(key,?result);
?????????????}else{
?????????????????????result?=?pool.get(key);
?????????????}
?????????????return?result;
?????}
}
下面做很小的改動,只修改了黑色字體部分。為了展示多線程的情況,我們寫一個多線程的類,如代碼清單28-11所示。
代碼清單28-11 多線程場景
public?class?MultiThread?extends?Thread?{
?????private?SignInfo?signInfo;
?????public?MultiThread(SignInfo?_signInfo){
?????????????????????this.signInfo?=?_signInfo;
?????}
?????public?void?run(){
?????????????if(!signInfo.getId().equals(signInfo.getLocation())){
?????????????????????System.out.println("編號:"+signInfo.getId());
?????????????????????System.out.println("考試地址:"+signInfo.getLocation());
?????????????????????System.out.println("線程不安全了!");
?????????????}
?????}
}
在run方法中判斷特殊值,檢查是否是線程安全,我們來看看場景類,如代碼清單28-12所示。
代碼清單28-12 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//在對象池中初始化4個對象
?????????????SignInfoFactory.getSignInfo("科目1");
?????????????SignInfoFactory.getSignInfo("科目2");
?????????????SignInfoFactory.getSignInfo("科目3");
?????????????SignInfoFactory.getSignInfo("科目4");
?????????????//取得對象
?????????????SignInfo?signInfo?=?SignInfoFactory.getSignInfo("科目2");
?????????????while(true){
?????????????????????signInfo.setId("ZhangSan");
?????????????????????signInfo.setLocation("ZhangSan");
?????????????????????(new?MultiThread(signInfo)).start();
?????????????????????signInfo.setId("LiSi");
?????????????????????signInfo.setLocation("LiSi");
?????????????????????(new?MultiThread(signInfo)).start();
?????????????}
?????}
}
模擬實際的多線程情況,在對象池中我們保留4個對象,然后啟動N多個線程來模擬,我們馬上就看到如下的提示:
編號:LiSi
考試地址:ZhangSan
線程不安全了!
看看,線程不安全了吧,這是正常的,設置的享元對象數量太少,導致每個線程都到對象池中獲得對象,然后都去修改其屬性,于是就出現一些不和諧數據。只要使用Java開發,線程問題是不可避免的,那我們怎么去避免這個問題呢?享元模式是讓我們使用共享技術,而Java的多線程又有如此問題,該如何設計呢?沒什么可以參考的標準,只有依靠經驗,在需要的地方考慮一下線程安全,在大部分的場景下都不用考慮。我們在使用享元模式時,對象池中的享元對象盡量多,多到足夠滿足業務為止。
28.4.2 性能平衡
盡量使用Java基本類型作為外部狀態。在報考系統中,我們不考慮系統的修改風險,完全可以重新建立一個類作為外部狀態,因為這才完全符合面向對象編程的理念。好,我們實現處理,先看類圖,如圖28-4所示。

圖28-4 類作為外部狀態
我們首先來看ExtrinsicState外部狀態類,如代碼清單28-13所示。
代碼清單28-13 外部狀態類
public?class?ExtrinsicState?{
?????//考試科目
?????private?String?subject;
?????//考試地點
?????private?String?location;
?????public?String?getSubject()?{
?????????????return?subject;
?????}
?????public?void?setSubject(String?subject)?{
?????????????this.subject?=?subject;
?????}
?????public?String?getLocation()?{
?????????????return?location;
?????}
?????public?void?setLocation(String?location)?{
?????????????this.location?=?location;
?????}
?????@Override
?????public?boolean?equals(Object?obj){
?????????????if(obj?instanceof?ExtrinsicState){
??????????????????ExtrinsicState?state?=?(ExtrinsicState)obj;
??????????????????return?state.getLocation().equals(location)?&&?state.getSubject().equals(subject);
?????????????}
?????????????return?false;
?????}
?????@Override
?????public?int?hashCode(){
?????????????return?subject.hashCode()?+?location.hashCode();
?????}
}
注意,一定要覆寫equals和hashCode方法,否則它作為HashMap中的key值是根本沒有意義的,只有hashCode值相等,并且equals返回結果為true,兩個對象才相等,也只有在這種情況下才有可能從對象池中查找獲得對象。
注意 如果把一個對象作為Map類的鍵值,一定要確保重寫了equals和hashCode方法,否則會出現通過鍵值搜索失敗的情況,例如map.get(object)、map.contains(object)等會返回失敗的結果。
SignInfo的修改較小,僅在SignInfo中引入該ExtrinsicState外部狀態對象,在此不再贅述。我們再來看享元工廠,它是以ExtrinsicState類作為外部狀態,如代碼清單28-14所示。
代碼清單28-14 享元工廠
public?class?SignInfoFactory?{
?????//池容器
?????private?static?HashMap<ExtrinsicState,SignInfo>?pool?=?new?HashMap?<ExtrinsicState,SignInfo>();
?????//從池中獲得對象
?????public?static?SignInfo?getSignInfo(ExtrinsicState?key){
?????????????//設置返回對象
?????????????SignInfo?result?=?null;
?????????????//池中沒有該對象,則建立,并放入池中
?????????????if(!pool.containsKey(key)){
?????????????????????result?=?new?SignInfo();
?????????????????????pool.put(key,?result);
?????????????}else{
?????????????????????result?=?pool.get(key);
?????????????}
?????????????return?result;
?????}
}
重點是看看我們的場景類,我們來測試一下性能差異,如代碼清單28-15所示。
代碼清單28-15 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//初始化對象池
?????????????ExtrinsicState?state1?=?new?ExtrinsicState();
?????????????state1.setSubject("科目1");
?????????????state1.setLocation("上海");
?????????????SignInfoFactory.getSignInfo(state1);
?????????????ExtrinsicState?state2?=?new?ExtrinsicState();
?????????????state2.setSubject("科目1");
?????????????state2.setLocation("上海");
?????????????//計算執行100萬次需要的時間
?????????????long?currentTime?=?System.currentTimeMillis();
?????????????for(int?i=0;i<1000000;i++){
?????????????????????SignInfoFactory.getSignInfo(state2);
?????????????}
?????????????long?tailTime?=?System.currentTimeMillis();
?????????????System.out.println("執行時間:"+(tailTime?-?currentTime)?+?"?ms");
?????}
}
運行結果如下所示:
執行時間:172 ms
同樣,我們看看以String類型作為外部狀態的運行情況,如代碼清單28-16所示。
代碼清單28-16 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????String?key1?=?"科目1上海";
?????????????String?key2?=?"科目1上海";
?????????????//初始化對象池
?????????????SignInfoFactory.getSignInfo(key1);
?????????????//計算執行10萬次需要的時間
?????????????long?currentTime?=?System.currentTimeMillis();
?????????????for(int?i=0;i<10000000;i++){
?????????????????????SignInfoFactory.getSignInfo(key2);
?????????????}
?????????????long?tailTime?=?System.currentTimeMillis();
?????????????System.out.println("執行時間:"+(tailTime?-?currentTime)?+?"?ms");
?????}
}
運行結果如下所示:
執行時間:78 ms
看到沒?一半的效率,這還是非常簡單的享元對象,看看我們重寫的equals方法和hashCode方法,這段代碼是必須實現的,如果比較復雜,這個時間差異會更大。
各位,想想看,使用自己編寫的類作為外部狀態,必須覆寫equals方法和hashCode方法,而且執行效率還比較低,這種吃力不討好的事情最好別做,外部狀態最好以Java的基本類型作為標志,如String、int等,可以大幅地提升效率。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖