天天看點

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

俗話說,自己寫的代碼,6個月後也是别人的代碼……複習!複習!複習!總結的知識點如下:

享元模式概念和實作例子

使用了享元模式的java api string類

java.lang.integer 的 valueof(int)方法源碼分析

使用享元模式的條件

享元模式和單例模式的差別

  前面的政策模式的話題提起了:如何解決政策類膨脹的問題,說到 “有時候可以通過把依賴于環境context類的狀态儲存到用戶端裡面,而将政策類設計成可共享的,這樣政策類執行個體可以被不同用戶端使用。”換言之,可以使用享元模式來減少對象的數量,享元模式它的英文名字叫flyweigh模式,又有人翻譯為羽量級模式,它是構造型模式之一,它通過與其他類似對象共享資料來減小記憶體占用,也正應了它的名字:享-分享。

  那麼到底是什麼意思呢?有什麼用呢?下面看個例子:我們有一個文檔,裡面寫了很多英文,大家知道英文字母有26個,大小寫一起一共是52個:

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  那麼我儲存這個檔案的時候,所有的單詞都占據了一份記憶體,每個字母都是一個對象,如果文檔裡的字母有重複的,怎麼辦?難道每次都要建立新的字母對象去儲存麼?答案是否定的,其實每個字母隻需要建立一次,然後把他們儲存起來,當再次使用的時候直接在已經建立好的字母裡取就ok了,這就是享元模式的一個思想的展現。說到這兒,其實想起了java的string類,這個類就是應用了享元模式。稍後再說,先看享元模式的類圖和具體實作例子。

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  抽象享元角色(接口或者抽象類):所有具體享元類的父類,規定一些需要實作的公共接口。

  具體享元角色:抽象享元角色的具體實作類,并實作了抽象享元角色規定的方法。

  享元工廠角色:負責建立和管理享元角色。它必須保證享元對象可以被系統适當地共享。當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象,如果系統中沒有一個适當的享元對象的話,享元工廠角色就應當建立一個合适的享元對象。代碼如下:

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

下面使用用戶端調用,先看看普通的方法

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

下面使用享元模式,必須指出的是,使用享元模式,那麼用戶端絕對不可以直接将具體享元類執行個體化,而必須通過一個工廠得到享元對象。

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  類圖如下:

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  一般而言,享元工廠對象在整個系統中隻有一個,是以也可以使用單例模式,由工廠方法産生所需要的享元對象。且設計模式不用拘泥于具體代碼, 代碼實作可能有n多種方式,n多語言……再看一例子,有老師類,繼承person類,老師類裡儲存一個數字編号,用戶端可以通過它來找到對應的老師。

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

用戶端調用

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

類圖

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  小結,到底系統需要滿足什麼樣的條件才能使用享元模式。對于這個問題,總結出以下幾點: 

一個系統中存在着大量的細粒度對象,且這些細粒度對象耗費了大量的記憶體。 

這些細粒度對象的狀态中的大部分都可以外部化

這些細粒度對象可以按照内蘊狀态分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。 

軟體系統不依賴于這些對象的身份,換言之,這些對象可以是不可分辨的。

滿足以上的這些條件的系統可以使用享元模型。最後,使用享元模式需要維護一個記錄了系統已有的所有享元的哈希表,也稱之為對象池,而這也需要耗費一定的資源。是以應當在有足夠多的享元執行個體可供共享時才值得使用享元模式。

  好了,現在多了幾個新的概念(外部化,内蘊,外蘊……),再次用教科書的理論,分析之前的享元模式的例子和概念:

  内蘊狀态:存儲在享元對象内部的對象,并且這些對象是不會随環境的改變而有所不同。是以,一個享元可以具有内蘊狀态并可以共享。

  外蘊狀态:随環境的改變而改變、不可以共享的對象。享元對象的外蘊狀态必須由用戶端儲存,并在享元對象被建立之後,在需要使用的時候再傳入到享元對象内部。外蘊狀态不可以影響享元對象的内蘊狀态,它們是互相獨立的。

  享元對象能做到共享的關鍵是區分内蘊狀态(internal state)和外蘊狀态(external state)。回憶之前的例子:最近的teacher例子,具體享元角色類teacher類其實就是有一個内蘊狀态,在本例中一個int類型的number屬性,它的值應當在享元對象被建立時賦予,也就是内蘊狀态對象讓享元對象自己去儲存,且可以被用戶端共享,所有的内蘊狀态在對象建立之後,就不會再改變了。下面看一個具有外蘊狀态的享元模式:

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  如果一個享元對象有外蘊狀态的話,所有的外部狀态都必須存儲在用戶端,在使用享元對象時,再由用戶端傳入享元對象。這裡隻有一個外蘊狀态,connect()方法的參數就是由外部傳入的外蘊狀态

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  享元工廠

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

雖然用戶端申請了三個享元對象,但是實際建立的享元對象隻有兩個,這就是共享的含義

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

  在jdk中有哪些使用享元模式的例子?舉例說明。

  說兩個,第一個是string類,第二個是java.lang.integer 的 valueof(int)方法。針對string,也是老生常談了,它是final的,字元串常量通常是在編譯的時候就确定好的,定義在類的方法區裡,也就是說,不同的類,即使用了同樣的字元串, 還是屬于不同的對象。是以才需要通過引用字元串常量來減少相同的字元串的數量。

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

使用相同的字元序列而不是使用new關鍵字建立的兩個字元串會建立指向java字元串常量池中的同一個字元串的指針。字元串常量池是java節約資源的一種方式。其實就是使用了享元模式的思想。字元串的配置設定,和其他的對象配置設定一樣,耗費高昂的時間與空間代價。jvm為了提高性能和減少記憶體開銷,在執行個體化字元串常量的時候進行了一些優化。為了減少在jvm中建立的字元串的數量,字元串類維護了一個字元串池,每當代碼建立字元串常量時,jvm會首先檢查字元串常量池。如果字元串已經存在池中,就傳回池中的執行個體引用。如果字元串不在池中,就會執行個體化一個字元串并放到池中。java能夠進行這樣的優化是因為字元串是不可變的,可以不用擔心資料沖突進行共享。

  java.lang.integer 的 valueof(int)方法源碼分析(8.0版本)

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

再看一例子;

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

很容易了解,再看

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

怎麼還是false呢?看看到底發生了什麼,反編譯上述程式;

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

我發現每次都是使用了自動裝箱

再看該方法源碼;

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

我發現,當使用integer的自動裝箱的時候,值在low和high之間時,會用緩存儲存起來,供多次使用,以節約記憶體。如果不在這個範圍内,則建立一個新的integer對象。這不就是尼瑪享元模式嗎!看看範圍:-128~+127

減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析
減小記憶體的占用問題——享元模式和單例模式的對比分析結合Java多線程的記憶體模型對線程安全的單例模式總結分析

   

  享元模式的缺陷是什麼?

  享元模式的優點在于它大幅度地降低記憶體中對象的數量。但是,它做到這一點所付出的代價也是很高的:

  ●  享元模式使得系統更加複雜。為了使對象可以共享,需要将一些狀态外部化,這使得程式的邏輯複雜化。

  ●  享元模式将享元對象的狀态外部化,而讀取外部狀态使得運作時間稍微變長。

  簡單看看,單例模式還得單開文章總結,涉及到了枚舉實作和記憶體模型:單例類隻能有一個執行個體,單例類必須自己建立自己的唯一執行個體,單例類必須給所有其他對象提供這一執行個體(提供全局通路點)。

  小結:享元模式和單例模式的異同

  享元是對象級别的, 也就是說在多個使用到這個對象的地方都隻需要使用這一個對象即可滿足要求, 而單例是類級别的, 就是說這個類必須隻能執行個體化出來一個對象, 可以這麼說, 單例是享元的一種特例, 設計模式不用拘泥于具體代碼, 代碼實作可能有n多種方式, 而單例可以看做是享元的實作方式中的一種, 但是他比享元更加嚴格的控制了對象的唯一性。

  

辛苦的勞動,轉載請注明出處,謝謝……

http://www.cnblogs.com/kubixuesheng/p/5174025.html