一.什么是單例
單例對象的類必須保證只有一個實例存在
對單例的實現(xiàn)可以分為兩大類——懶漢式和餓漢式,他們的區(qū)別在于:
懶漢式:指全局的單例實例在第一次被使用時構(gòu)建。
餓漢式:指全局的單例實例在類裝載時構(gòu)建。
懶漢式單例
1.簡單版本

缺點:當(dāng)多線程工作的時候,如果有多個線程同時運行到if (instance == null),都判斷為null,那么兩個線程就各自會創(chuàng)建一個實例——這樣一來,就不是單例了。
改進 ...
加上synchronized關(guān)鍵字

缺點:雖然解決了多個線程多個實例的問題 但是當(dāng)一個線程執(zhí)行到getInstance()時其他線程就要進入等待狀態(tài) 實際上會對程序的執(zhí)行效率造成負(fù)面影響
繼續(xù)改進 ..
雙重檢查

實際上就是合并了上面兩個解決辦法
第一個if (instance == null),其實是為了解決Version2中的效率問題,只有instance為null的時候,才進入synchronized的代碼段——大大減少了幾率。
第二個if (instance == null),則是跟Version2一樣,是為了防止可能出現(xiàn)多個實例的情況
其實 還是有小概率會出現(xiàn)問題 ?主要是設(shè)計 原子操作 和 指令重排的概念
主要在于singleton?= new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。
1. 給 singleton?分配內(nèi)存
2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量,形成實例
3. 將singleton對象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null 了)
但是在 JVM 的即時編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。
再稍微解釋一下,就是說,由于有一個『instance已經(jīng)不為null但是仍沒有完成初始化』的中間狀態(tài),而這個時候,如果有其他線程剛好運行到第一層if (instance == null)這里,這里讀取到的instance已經(jīng)不為null了,所以就直接把這個中間狀態(tài)的instance拿去用了,就會產(chǎn)生問題。
這里的關(guān)鍵在于——線程T1對instance的寫操作沒有完成,線程T2就執(zhí)行了讀操作。
繼續(xù)改進....
終極版本 volatile關(guān)鍵字

對于上面的版本雖然已經(jīng)很大程度上改善了 但是畢竟還是有幾率會出現(xiàn)問題的
解決方案就是給instance生明加上volatile關(guān)鍵字
volatile關(guān)鍵字的一個作用是禁止指令重排,把instance聲明為volatile之后,對它的寫操作就會有一個內(nèi)存屏障(什么是內(nèi)存屏障?),這樣,在它的賦值完成之前,就不用會調(diào)用讀操作。
注意:volatile阻止的不singleton?= new Singleton()這句話內(nèi)部[1-2-3]的指令重排,而是保證了在一個寫操作([1-2-3])完成之前,不會調(diào)用讀操作(if (instance == null))。
到了這個版本 確實沒什么問題了 但是就是太復(fù)雜了。。。。
上面介紹了 懶漢式的一些基本寫法 和改進后的寫法 下面介紹一些餓漢式 相比懶漢式就簡單多了
餓漢式單例實現(xiàn)
注:開頭說過 餓漢式單例是指全局的單例實例在類裝載時構(gòu)建的實現(xiàn)方式
由于類裝載的過程是由類加載器(ClassLoader)來執(zhí)行的,這個過程也是由JVM來保證同步的,所以這種方式先天就有一個優(yōu)勢能夠免疫許多由多線程引起的問題

當(dāng)然萬事萬物沒有完美的 如果非要對上面挑點毛病出來就是 由于INSTANCE的初始化是在類加載時進行的 而類的加載是由ClassLoader來做的 所以開發(fā)者很難把握它的初始化時機
1.可能由于初始化太早造成資源浪費
2.如果初始化本身依賴于一些其他數(shù)據(jù),那么也就很難保證其他數(shù)據(jù)會在它初始化之前準(zhǔn)備好。
一些其他的實現(xiàn)方式:
《Effective Java 1》? —— 靜態(tài)內(nèi)部類

這種寫法非常巧妙:
對于內(nèi)部類SingletonHolder,它是一個餓漢式的單例實現(xiàn),在SingletonHolder初始化的時候會由ClassLoader來保證同步,使INSTANCE是一個真·單例。
同時,由于SingletonHolder是一個內(nèi)部類,只在外部類的Singleton的getInstance()中被使用,所以它被加載的時機也就是在getInstance()方法第一次被調(diào)用的時候。
——它利用了ClassLoader來保證了同步,同時又能讓開發(fā)者控制類加載的時機。從內(nèi)部看是一個餓漢式的單例,但是從外部看來,又的確是懶漢式的實現(xiàn)。
《Effective Java 2》 —— 枚舉

由于創(chuàng)建枚舉實例的過程是線程安全的,所以這種寫法也沒有同步的問題。
但是在需要繼承的場景,它就不適用了
總結(jié):任何一種方法都有他的用武之地 只是需要考慮一個度的問題 就需要開發(fā)者在開發(fā)中選擇自己適合的單例就好了。