一、餓漢式單例類
特點(diǎn):餓漢式提前實(shí)例化,沒(méi)有懶漢式中多線程問(wèn)題,但不管我們是不是調(diào)用getInstance()都會(huì)存在一個(gè)實(shí)例在內(nèi)存中
二、內(nèi)部類式單例類
特點(diǎn):內(nèi)部類式中,實(shí)現(xiàn)了延遲加載,只有我們調(diào)用了getInstance(),才會(huì)創(chuàng)建唯一的實(shí)例到內(nèi)存中.并且也解決了懶漢式中多線程的問(wèn)題.解決的方式是利用了Classloader的特性.
三、懶漢式單例類
特點(diǎn):在懶漢式中,有線程A和B,當(dāng)線程A運(yùn)行到第8行時(shí),跳到線程B,當(dāng)B也運(yùn)行到8行時(shí),兩個(gè)線程的instance都為空,這樣就會(huì)生成兩個(gè)實(shí)例。解決的辦法是同步:
可以同步但是效率不高:
這樣寫程序不會(huì)出錯(cuò),因?yàn)檎麄€(gè)getInstance是一個(gè)整體的"critical section",但就是效率很不好,因?yàn)槲覀兊哪康钠鋵?shí)只是在第一個(gè)初始化instance的時(shí)候需要locking(加鎖),而后面取用instance的時(shí)候,根本不需要線程同步。
于是聰明的人們想出了下面的做法:
雙檢鎖寫法:
思路很簡(jiǎn)單,就是我們只需要同步(synchronize)初始化instance的那部分代碼從而使代碼既正確又很有效率。
這就是所謂的“雙檢鎖”機(jī)制(顧名思義)。
很可惜,這樣的寫法在很多平臺(tái)和優(yōu)化編譯器上是錯(cuò)誤的。
原因在于:instance = new Singleton()這行代碼在不同編譯器上的行為是無(wú)法預(yù)知的。一個(gè)優(yōu)化編譯器可以合法地如下實(shí)現(xiàn)instance = new Singleton():
1. instance = 給新的實(shí)體分配內(nèi)存
2. 調(diào)用Singleton的構(gòu)造函數(shù)來(lái)初始化instance的成員變量
現(xiàn)在想象一下有線程A和B在調(diào)用getInstance,線程A先進(jìn)入,在執(zhí)行到步驟1的時(shí)候被踢出了cpu。然后線程B進(jìn)入,B看到的是instance 已經(jīng)不是null了(內(nèi)存已經(jīng)分配),于是它開始放心地使用instance,但這個(gè)是錯(cuò)誤的,因?yàn)樵谶@一時(shí)刻,instance的成員變量還都是缺省值,A還沒(méi)有來(lái)得及執(zhí)行步驟2來(lái)完成instance的初始化。
當(dāng)然編譯器也可以這樣實(shí)現(xiàn):
1. temp = 分配內(nèi)存
2. 調(diào)用temp的構(gòu)造函數(shù)
3. instance = temp
如果編譯器的行為是這樣的話我們似乎就沒(méi)有問(wèn)題了,但事實(shí)卻不是那么簡(jiǎn)單,因?yàn)槲覀儫o(wú)法知道某個(gè)編譯器具體是怎么做的,因?yàn)樵贘ava的memory model里對(duì)這個(gè)問(wèn)題沒(méi)有定義。
雙檢鎖對(duì)于基礎(chǔ)類型(比如int)適用。很顯然吧,因?yàn)榛A(chǔ)類型沒(méi)有調(diào)用構(gòu)造函數(shù)這一步。
新聞熱點(diǎn)
疑難解答
圖片精選