Android單例模式詳解

一.什么是單例

單例對象的類必須保證只有一個實例存在

對單例的實現(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ā)中選擇自己適合的單例就好了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容