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