研磨设计模式之 单例模式-3
3.3? 延遲加載的思想
??????? 單例模式的懶漢式實現(xiàn)方式體現(xiàn)了延遲加載的思想,什么是延遲加載呢?
??????? 通俗點說,就是一開始不要加載資源或者數(shù)據(jù),一直等,等到馬上就要使用這個資源或者數(shù)據(jù)了,躲不過去了才加載,所以也稱Lazy Load,不是懶惰啊,是“延遲加載”,這在實際開發(fā)中是一種很常見的思想,盡可能的節(jié)約資源。
??????? 體現(xiàn)在什么地方呢?看如下代碼:
?
3.4? 緩存的思想
??????? 單例模式的懶漢式實現(xiàn)還體現(xiàn)了緩存的思想,緩存也是實際開發(fā)中非常常見的功能。
??????? 簡單講就是,如果某些資源或者數(shù)據(jù)會被頻繁的使用,而這些資源或數(shù)據(jù)存儲在系統(tǒng)外部,比如數(shù)據(jù)庫、硬盤文件等,那么每次操作這些數(shù)據(jù)的時候都從數(shù)據(jù)庫或者硬盤上去獲取,速度會很慢,會造成性能問題。
??????? 一個簡單的解決方法就是:把這些數(shù)據(jù)緩存到內(nèi)存里面,每次操作的時候,先到內(nèi)存里面找,看有沒有這些數(shù)據(jù),如果有,那么就直接使用,如果沒有那么就獲取它,并設(shè)置到緩存中,下一次訪問的時候就可以直接從內(nèi)存中獲取了。從而節(jié)省大量的時間,當(dāng)然,緩存是一種典型的空間換時間的方案。
??????? 緩存在單例模式的實現(xiàn)中怎么體現(xiàn)的呢?
?3.5? Java中緩存的基本實現(xiàn)
??????? 引申一下,看看在Java開發(fā)中的緩存的基本實現(xiàn),在Java中最常見的一種實現(xiàn)緩存的方式就是使用Map,基本的步驟是:
- 先到緩存里面查找,看看是否存在需要使用的數(shù)據(jù)
- 如果沒有找到,那么就創(chuàng)建一個滿足要求的數(shù)據(jù),然后把這個數(shù)據(jù)設(shè)置回到緩存中,以備下次使用
- 如果找到了相應(yīng)的數(shù)據(jù),或者是創(chuàng)建了相應(yīng)的數(shù)據(jù),那就直接使用這個數(shù)據(jù)。
?還是看看示例吧,示例代碼如下:
/*** Java中緩存的基本實現(xiàn)示例*/ public class JavaCache {/*** 緩存數(shù)據(jù)的容器,定義成Map是方便訪問,直接根據(jù)Key就可以獲取Value了* key選用String是為了簡單,方便演示*/private Map<String,Object> map = new HashMap<String,Object>();/*** 從緩存中獲取值* @param key 設(shè)置時候的key值* @return key對應(yīng)的Value值*/public Object getValue(String key){//先從緩存里面取值Object obj = map.get(key);//判斷緩存里面是否有值if(obj == null){//如果沒有,那么就去獲取相應(yīng)的數(shù)據(jù),比如讀取數(shù)據(jù)庫或者文件//這里只是演示,所以直接寫個假的值obj = key+",value";//把獲取的值設(shè)置回到緩存里面map.put(key, obj);}//如果有值了,就直接返回使用return obj;} }??????? 這里只是緩存的基本實現(xiàn),還有很多功能都沒有考慮,比如緩存的清除,緩存的同步等等。當(dāng)然,Java的緩存還有很多實現(xiàn)方式,也是非常復(fù)雜的,現(xiàn)在有很多專業(yè)的緩存框架,更多緩存的知識,這里就不再去討論了。
?
3.6? 利用緩存來實現(xiàn)單例模式
??????? 其實應(yīng)用Java緩存的知識,也可以變相實現(xiàn)Singleton模式,算是一個模擬實現(xiàn)吧。每次都先從緩存中取值,只要創(chuàng)建一次對象實例過后,就設(shè)置了緩存的值,那么下次就不用再創(chuàng)建了。
??????? 雖然不是很標(biāo)準(zhǔn)的做法,但是同樣可以實現(xiàn)單例模式的功能,為了簡單,先不去考慮多線程的問題,示例代碼如下:
??????? 是不是也能實現(xiàn)單例所要求的功能呢?其實實現(xiàn)模式的方式有很多種,并不是只有模式的參考實現(xiàn)所實現(xiàn)的方式,上面這種也能實現(xiàn)單例所要求的功能,只不過實現(xiàn)比較麻煩,不是太好而已,但在后面擴展單例模式的時候會有用。
??????? 另外,模式是經(jīng)驗的積累,模式的參考實現(xiàn)并不一定是最優(yōu)的,對于單例模式,后面會給大家一些更好的實現(xiàn)方式。
3.7? 單例模式的優(yōu)缺點
?
1:時間和空間
??????? 比較上面兩種寫法:懶漢式是典型的時間換空間,也就是每次獲取實例都會進(jìn)行判斷,看是否需要創(chuàng)建實例,費判斷的時間,當(dāng)然,如果一直沒有人使用的話,那就不會創(chuàng)建實例,節(jié)約內(nèi)存空間。
??????? 餓漢式是典型的空間換時間,當(dāng)類裝載的時候就會創(chuàng)建類實例,不管你用不用,先創(chuàng)建出來,然后每次調(diào)用的時候,就不需要再判斷了,節(jié)省了運行時間。
2:線程安全
(1)從線程安全性上講,不加同步的懶漢式是線程不安全的,比如說:有兩個線程,一個是線程A,一個是線程B,它們同時調(diào)用getInstance方法,那就可能導(dǎo)致并發(fā)問題。如下示例:
?程序繼續(xù)運行,兩個線程都向前走了一步,如下:
?
可能有些朋友會覺得文字描述還是不夠直觀,再來畫個圖說明一下,如圖4所示:
?????????????????????????????????????????????????????? 圖4? 懶漢式單例的線程問題示意圖
??????? 通過圖4的分解描述,明顯可以看出,當(dāng)A、B線程并發(fā)的情況下,會創(chuàng)建出兩個實例來,也就是單例的控制在并發(fā)情況下失效了。
(2)餓漢式是線程安全的,因為虛擬機保證了只會裝載一次,在裝載類的時候是不會發(fā)生并發(fā)的。
(3)如何實現(xiàn)懶漢式的線程安全呢?
??????? 當(dāng)然懶漢式也是可以實現(xiàn)線程安全的,只要加上synchronized即可,如下:
???????? 但是這樣一來,會降低整個訪問的速度,而且每次都要判斷,也確實是稍微慢點。那么有沒有更好的方式來實現(xiàn)呢?
(4)雙重檢查加鎖
??????? 可以使用“雙重檢查加鎖”的方式來實現(xiàn),就可以既實現(xiàn)線程安全,又能夠使性能不受到大的影響。那么什么是“雙重檢查加鎖”機制呢?
??????? 所謂雙重檢查加鎖機制,指的是:并不是每次進(jìn)入getInstance方法都需要同步,而是先不同步,進(jìn)入方法過后,先檢查實例是否存在,如果不存在才進(jìn)入下面的同步塊,這是第一重檢查。進(jìn)入同步塊過后,再次檢查實例是否存在,如果不存在,就在同步的情況下創(chuàng)建一個實例,這是第二重檢查。這樣一來,就只需要同步一次了,從而減少了多次在同步情況下進(jìn)行判斷所浪費的時間。
??????? 雙重檢查加鎖機制的實現(xiàn)會使用一個關(guān)鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內(nèi)存,從而確保多個線程能正確的處理該變量。
??????? 注意:在Java1.4及以前版本中,很多JVM對于volatile關(guān)鍵字的實現(xiàn)有問題,會導(dǎo)致雙重檢查加鎖的失敗,因此雙重檢查加鎖的機制只能用在Java5及以上的版本。
??????? 看看代碼可能會更清楚些,示例代碼如下:
???????? 這種實現(xiàn)方式既可使實現(xiàn)線程安全的創(chuàng)建實例,又不會對性能造成太大的影響,它只是在第一次創(chuàng)建實例的時候同步,以后就不需要同步了,從而加快運行速度。
???????? 提示:由于volatile關(guān)鍵字可能會屏蔽掉虛擬機中一些必要的代碼優(yōu)化,所以運行效率并不是很高,因此一般建議,沒有特別的需要,不要使用。也就是說,雖然可以使用雙重加鎖機制來實現(xiàn)線程安全的單例,但并不建議大量采用,根據(jù)情況來選用吧。
?
未完待續(xù)
?
轉(zhuǎn)載于:https://www.cnblogs.com/sjms/archive/2010/08/30/1812303.html
總結(jié)
以上是生活随笔為你收集整理的研磨设计模式之 单例模式-3的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动界面设计点滴:工欲善其事,必先利其器
- 下一篇: Oracle 索引扫描的五种类型