漫画:什么是单例设计模式
轉載自?永遠愛大家的 程序員小灰
—————? 第二天? —————
單例模式第一版:
| 1 2 3 4 5 6 7 8 9 10 11 | public?class?Singleton?{ ????private?Singleton()?{}??//私有構造函數 ????private?static?Singleton?instance?=?null;??//單例對象 ????//靜態工廠方法 ????public?static?Singleton?getInstance()?{ ????????if?(instance?==?null)?{ ????????????instance?=?new?Singleton(); ????????} ????????return?instance; ????} } |
為什么這樣寫呢?我們來解釋幾個關鍵點:
1、要想讓一個類只能構建一個對象,自然不能讓它隨便去做new操作,因此Signleton的構造方法是私有的。
2、instance是Singleton類的靜態成員,也是我們的單例對象。它的初始值可以寫成Null,也可以寫成new?Singleton()。至于其中的區別后來會做解釋。
3、getInstance是獲取單例對象的方法。
如果單例初始值是null,還未構建,則構建單例對象并返回。這個寫法屬于單例模式當中的懶漢模式。
如果單例對象一開始就被new?Singleton()主動構建,則不再需要判空操作,這種寫法屬于餓漢模式。
這兩個名字很形象:餓漢主動找食物吃,懶漢躺在地上等著人喂。
為什么說剛才的代碼不是線程安全呢?
假設Singleton類剛剛被初始化,instance對象還是空,這時候兩個線程同時訪問getInstance方法:
因為Instance是空,所以兩個線程同時通過了條件判斷,開始執行new操作:
這樣一來,顯然instance被構建了兩次。讓我們對代碼做一下修改:
單例模式第二版:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public?class?Singleton?{ ????private?Singleton()?{}??//私有構造函數 ???private?static?Singleton?instance?=?null;??//單例對象 ???//靜態工廠方法 ???public?static?Singleton?getInstance()?{ ????????if?(instance?==?null)?{??????//雙重檢測機制 ?????????synchronized?(Singleton.class){??//同步鎖 ???????????if?(instance?==?null)?{?????//雙重檢測機制 ?????????????instance?=?new?Singleton(); ???????????????} ????????????} ?????????} ????????return?instance; ????} } |
為什么這樣寫呢?我們來解釋幾個關鍵點:
1、為了防止new Singleton被執行多次,因此在new操作之前加上Synchronized 同步鎖,鎖住整個類(注意,這里不能使用對象鎖)。
2、進入Synchronized?臨界區以后,還要再做一次判空。因為當兩個線程同時訪問的時候,線程A構建完對象,線程B也已經通過了最初的判空驗證,不做第二次判空的話,線程B還是會再次構建instance對象。
像這樣兩次判空的機制叫做雙重檢測機制。
————————————
假設這樣的場景,當兩個線程一先一后訪問getInstance方法的時候,當A線程正在構建對象,B線程剛剛進入方法:
這種情況表面看似沒什么問題,要么Instance還沒被線程A構建,線程B執行 if(instance == null)的時候得到false;要么Instance已經被線程A構建完成,線程B執行 if(instance == null)的時候得到true。
真的如此嗎?答案是否定的。這里涉及到了JVM編譯器的指令重排。
指令重排是什么意思呢?比如java中簡單的一句 instance = new Singleton,會被編譯器編譯成如下JVM指令:
memory =allocate();? ? //1:分配對象的內存空間?
ctorInstance(memory);? //2:初始化對象?
instance =memory;? ? ?//3:設置instance指向剛分配的內存地址?
但是這些指令順序并非一成不變,有可能會經過JVM和CPU的優化,指令重排成下面的順序:
memory =allocate();? ? //1:分配對象的內存空間?
instance =memory;? ? ?//3:設置instance指向剛分配的內存地址?
ctorInstance(memory);? //2:初始化對象?
當線程A執行完1,3,時,instance對象還未完成初始化,但已經不再指向null。此時如果線程B搶占到CPU資源,執行??if(instance == null)的結果會是false,從而返回一個沒有初始化完成的instance對象。如下圖所示:
如何避免這一情況呢?我們需要在instance對象前面增加一個修飾符volatile。
單例模式第三版:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public?class?Singleton?{ ????private?Singleton()?{}??//私有構造函數 ???private?volatile?static?Singleton?instance?=?null;??//單例對象 ???//靜態工廠方法 ???public?static?Singleton?getInstance()?{ ???????if?(instance?==?null)?{??????//雙重檢測機制 ???????synchronized?(this){??//同步鎖 ?????????if?(instance?==?null)?{?????//雙重檢測機制 ???????????instance?=?new?Singleton(); ?????????????} ??????????} ???????} ???????return?instance; ????} } |
The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.
經過volatile的修飾,當線程A執行instance = new Singleton的時候,JVM執行順序是什么樣?始終保證是下面的順序:
memory =allocate();? ? //1:分配對象的內存空間?
ctorInstance(memory);? //2:初始化對象?
instance =memory;? ? ?//3:設置instance指向剛分配的內存地址?
如此在線程B看來,instance對象的引用要么指向null,要么指向一個初始化完畢的Instance,而不會出現某個中間態,保證了安全。
幾點說明:
1、volatile關鍵字不但可以防止指令重排,也可以保證線程訪問的變量值是主內存中的最新值。有關volatile的詳細原理,我在以后的漫畫中會專門講解。
2、本漫畫純屬娛樂,還請大家盡量珍惜當下的工作,切勿模仿小灰的行為哦。
總結
以上是生活随笔為你收集整理的漫画:什么是单例设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 漫画:什么是volatile关键字?(整
- 下一篇: 设计模式之静态代理模式实战