01?背景
????以我們常用辦公軟件WPS為例,我們使用的時(shí)候期望點(diǎn)擊一次工具欄彈出一個(gè)對(duì)話框初厚,再次點(diǎn)擊的時(shí)候仍然是當(dāng)前對(duì)話框忆谓,而不是出現(xiàn)多個(gè)對(duì)話框。反映到編程中秋泳,其實(shí)就是對(duì)話框只被實(shí)例化一次潦闲,這就是單例模式的一個(gè)應(yīng)用場(chǎng)景。
02?概述
????單例模式(Singleton Pattern):?jiǎn)卫J酱_保某一個(gè)類(lèi)只有一個(gè)實(shí)例迫皱,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例歉闰,這個(gè)類(lèi)稱(chēng)為單例類(lèi),它提供全局訪問(wèn)的方法。
????根據(jù)定義可知單例模式的要點(diǎn)有三個(gè):
??? 1和敬、某個(gè)類(lèi)只能有一個(gè)實(shí)例凹炸;
????2、它必須自行創(chuàng)建這個(gè)實(shí)例昼弟;
??? 3啤它、它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
????單例模式是一種對(duì)象創(chuàng)建型模式舱痘。單例模式又名單件模式或單態(tài)模式变骡。
03?實(shí)現(xiàn)
3.1 餓漢式
????餓漢法就是在第一次引用該類(lèi)的時(shí)候就創(chuàng)建對(duì)象實(shí)例,而不管實(shí)際是否需要?jiǎng)?chuàng)建衰粹。一上來(lái)就先實(shí)例化锣光,但若類(lèi)沒(méi)有使用的話笆怠,就有點(diǎn)浪費(fèi)資源類(lèi)铝耻。
? ??優(yōu)點(diǎn):這樣做的好處是編寫(xiě)簡(jiǎn)單,無(wú)需關(guān)注線程安全問(wèn)題蹬刷。
? ??缺點(diǎn):
????1瓢捉、會(huì)生產(chǎn)出過(guò)多的實(shí)例對(duì)象,無(wú)論你是否要使用他們办成。
????2泡态、無(wú)法做到延遲創(chuàng)建對(duì)象,但是我們很多時(shí)候都希望對(duì)象可以盡可能地延遲加載迂卢,從而減小負(fù)載某弦,所以需要懶漢法。
3.2?懶漢式
????餓漢式(又稱(chēng)飽漢模式)而克,很飽不著急靶壮,延遲加載,啥時(shí)候用啥時(shí)候創(chuàng)建實(shí)例员萍,存在線程安全問(wèn)題腾降。
????實(shí)例在開(kāi)始時(shí)為空,第一次加載后才實(shí)例化碎绎◇θ溃可節(jié)約一些資源,但在并發(fā)時(shí)有可能出現(xiàn)多個(gè)單例筋帖。
? ??優(yōu)點(diǎn):延時(shí)加載奸晴,用的時(shí)候才會(huì)生產(chǎn)對(duì)象。
? ??缺點(diǎn):存在線程安全問(wèn)題日麸,需要保證同步蚁滋,付出效率的代價(jià)。
? ??1、單線程實(shí)現(xiàn)
????這種寫(xiě)法是最簡(jiǎn)單的辕录,由私有構(gòu)造器和一個(gè)公有靜態(tài)工廠方法構(gòu)成睦霎,在工廠方法中對(duì)singleton進(jìn)行null判斷,如果是null就new一個(gè)出來(lái)走诞,最后返回singleton對(duì)象副女。
? ??優(yōu)點(diǎn):延遲加載,資源利用率高蚣旱,不執(zhí)行g(shù)etInstance()就不會(huì)被實(shí)例化碑幅,可以執(zhí)行該類(lèi)的其他靜態(tài)方法。
? ??缺點(diǎn):第一次加載時(shí)不夠快塞绿,線程不安全沟涨,多線程使用不必要的同步開(kāi)銷(xiāo)大。
? ??2异吻、多線程實(shí)現(xiàn)
? ??1)直接加鎖
? ??優(yōu)點(diǎn):線程安全裹赴。
? ??缺點(diǎn):代價(jià)太高(整個(gè)判空和申請(qǐng)階段全部都加鎖,一勞永逸诀浪,但是效率低棋返,鎖范圍太大)。
? ??2)雙檢查鎖
????雙重鎖模式雷猪,是飽漢模式的優(yōu)化睛竣,進(jìn)行雙重判斷,當(dāng)已經(jīng)創(chuàng)建過(guò)實(shí)例對(duì)象后就無(wú)需加鎖求摇,提高效率射沟。也是一種推薦使用的方式。
????只有對(duì)象為空的時(shí)候才加鎖与境,加完鎖后再判空验夯,防止在加鎖的過(guò)程中被另一個(gè)線程調(diào)用new,即進(jìn)行雙檢查:第一次檢查是避免代價(jià)過(guò)高的問(wèn)題嚷辅,第二次檢查是防止多線程問(wèn)題簿姨。
????注:可以看出來(lái)與直接加鎖相比,鎖的范圍縮小了簸搞,效率得以提升扁位,同時(shí)通過(guò)在申請(qǐng)實(shí)例前加鎖保證線程安全。
? ??優(yōu)點(diǎn):比直接加鎖效率高趁俊。
? ??缺點(diǎn):內(nèi)存讀寫(xiě)reorder不安全域仇。
????reorder問(wèn)題:一般new的執(zhí)行過(guò)程認(rèn)為是分配內(nèi)存->構(gòu)造函數(shù)初始化->返回地址,但是實(shí)際上可能是分配內(nèi)存->返回地址->構(gòu)造函數(shù)這種錯(cuò)亂的順序寺擂。如果線程1reorder暇务,另外一個(gè)線程2判斷非空直接返回pSingleton泼掠,但是這個(gè)對(duì)象實(shí)例是無(wú)法正常使用的,它只是一個(gè)還未調(diào)用構(gòu)造函數(shù)初始化的內(nèi)存垦细。
????所以編譯器需要解決這類(lèi)問(wèn)題择镇,即編譯器不能優(yōu)化。
????3)volatile
??? volatile這個(gè)關(guān)鍵字有兩層語(yǔ)義:
????第一層語(yǔ)義是可見(jiàn)性括改∧逋悖可見(jiàn)性指的是在一個(gè)線程中對(duì)該變量的修改會(huì)馬上由工作內(nèi)存(Work Memory)寫(xiě)回主內(nèi)存(Main Memory),所以會(huì)馬上反應(yīng)在其它線程的讀取操作中嘱能,即看到的都是最新的結(jié)果吝梅。
????第二層語(yǔ)義是禁止指令重排序優(yōu)化。我們寫(xiě)的代碼(尤其是多線程代碼)惹骂,由于編譯器優(yōu)化苏携,在實(shí)際執(zhí)行的時(shí)候可能與我們編寫(xiě)的順序不同。
3.3 靜態(tài)內(nèi)部類(lèi)
????有沒(méi)有一種延時(shí)加載对粪,并且能保證線程安全的簡(jiǎn)單寫(xiě)法呢右冻?我們可以把Singleton實(shí)例放到一個(gè)靜態(tài)內(nèi)部類(lèi)中,這樣就避免了靜態(tài)實(shí)例在Singleton類(lèi)加載的時(shí)候就創(chuàng)建對(duì)象衩侥,并且由于靜態(tài)內(nèi)部類(lèi)只會(huì)被加載一次国旷,所以這種寫(xiě)法也是線程安全的:
3.4 選擇
????一般采用餓漢式矛物,若對(duì)資源十分在意可以采用靜態(tài)內(nèi)部類(lèi)茫死,不建議采用懶漢式及雙重檢測(cè)。
04?特點(diǎn)
4.1 優(yōu)點(diǎn)
????1履羞、提供了對(duì)唯一實(shí)例的受控訪問(wèn)峦萎。因?yàn)閱卫?lèi)封裝了它的唯一實(shí)例,所以它可以嚴(yán)格控制客戶(hù)怎樣以及何時(shí)訪問(wèn)它忆首。
????2爱榔、只存在一個(gè)對(duì)象,節(jié)約系統(tǒng)資源糙及,對(duì)于一些需要頻繁創(chuàng)建和銷(xiāo)毀的對(duì)象详幽,單例模式無(wú)疑可以提高系統(tǒng)的性能。
4.2 缺點(diǎn)
????1浸锨、由于單例模式中沒(méi)有抽象層唇聘,因此單例類(lèi)的擴(kuò)展有很大的困難。
????2柱搜、單例類(lèi)的職責(zé)過(guò)重迟郎,在一定程度上違背了“單一職責(zé)原則”。因?yàn)閱卫?lèi)既充當(dāng)了工廠角色聪蘸,提供了工廠方法宪肖,同時(shí)又充當(dāng)了產(chǎn)品角色表制,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起控乾。
? ? 3么介、濫用單例將帶來(lái)一些負(fù)面問(wèn)題。現(xiàn)在很多面向?qū)ο笳Z(yǔ)言(如Java)的運(yùn)行環(huán)境都提供了自動(dòng)垃圾回收的技術(shù)蜕衡,因此夭拌,如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為它是垃圾衷咽,會(huì)自動(dòng)銷(xiāo)毀并回收資源鸽扁,下次利用時(shí)又將重新實(shí)例化,這將導(dǎo)致對(duì)象狀態(tài)的丟失镶骗。
05?應(yīng)用場(chǎng)景
????在以下情況下可以使用單例模式:
? ? 1桶现、系統(tǒng)只需要一個(gè)實(shí)例對(duì)象(線程池、緩存鼎姊、硬件設(shè)備等)骡和,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對(duì)象。
? ? 2相寇、客戶(hù)調(diào)用類(lèi)的單個(gè)實(shí)例只允許使用一個(gè)公共訪問(wèn)點(diǎn)慰于,除了該公共訪問(wèn)點(diǎn),不能通過(guò)其他途徑訪問(wèn)該實(shí)例唤衫。
? ? 3婆赠、一個(gè)具有自動(dòng)編號(hào)主鍵的表可以有多個(gè)用戶(hù)同時(shí)使用,但數(shù)據(jù)庫(kù)中只能有一個(gè)地方分配下一個(gè)主鍵編號(hào)佳励,否則會(huì)出現(xiàn)主鍵重復(fù)休里,因此該主鍵編號(hào)生成器必須具備唯一性,可以通過(guò)單例模式來(lái)實(shí)現(xiàn)赃承。