<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 403, comments - 310, trackbacks - 0, articles - 7
      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

    Singleton模式與雙檢測鎖定(DCL)

    Posted on 2008-04-07 21:58 ZelluX 閱讀(2324) 評論(7)  編輯  收藏 所屬分類: OOP

    轉載請注明 作者 ZelluX??? http://m.tkk7.com/zellux

    看OOP教材時,提到了一個雙檢測鎖定(Double-Checked Lock, DCL)的問題,但是書上沒有多介紹,只是說這是一個和底層內存機制有關的漏洞。查閱了下相關資料,對這個問題大致有了點了解。

    從頭開始說吧。

    在多線程的情況下Singleton模式會遇到不少問題,一個簡單的例子

    ?? 1:? class Singleton {?????
    ?? 2:????? private static Singleton instance = null;?????
    ?? 3:????????
    ?? 4:????? public static Singleton instance() {?????
    ?? 5:????????? if (instance == null) {?????
    ?? 6:????????????? instance = new Singleton();?????
    ?? 7:????????? }?????
    ?? 8:????????? return instance;????
    ?? 9:????? }????
    ?? 10:? }

    ??
    假設這樣一個場景,有兩個線程調用Singleton.instance(),首先線程一判斷instance是否等于null,判斷完后一瞬間虛擬機把線程二調度為運行線程,線程二再次判斷instance是否為null,然后創建一個Singleton實例,線程二的時間片用完后,線程一被喚醒,接下來它執行的代碼依然是instance = new Singleton();
    兩次調用返回了不同的對象,出現問題了。

    最簡單的方法自然是在類被載入時就初始化這個對象:private static Singleton instance = new Singleton();

    JLS(Java Language Specification)中規定了一個類只會被初始化一次,所以這樣做肯定是沒問題的。

    但是如果要實現延遲初始化(Lazy initialization),比如這個實例初始化時的參數要在運行期才能確定,應該怎么做呢?

    依然有最簡單的方法:使用synchronized關鍵字修飾初始化方法:

    ??? public synchronized static Singleton instance() {???????
    ??????? if (instance == null) {
    ??????????? instance = new Singleton();
    ??????? }
    ??????? return instance;
    ??? }

    ???
    這里有一個性能問題:多個線程同時訪問這個方法時,會因為同步而導致每次只有一個線程運行,影響程序性能。而事實上初始化完畢后只需要簡單的返回instance的引用就行了。

    DCL是一個“看似”有效的解決方法,先把對應代碼放上來吧:

    ??? 1 :?? class Singleton {??
    ??? 2 :?????? private?static?Singleton instance = null ;??
    ??? 3 :?????
    ??? 4 :?????? public?static Singleton instance() {??
    ??? 5 :?????????? if?(instance?== null ) {
    ??? 6 :?????????????? synchronized?(this) {??
    ??? 7 :?????????????????? if?(instance?==?null)
    ??? 8 :????????????????????? instance = new?Singleton();
    ??? 9 :????????????? }
    ??? 10 :????????? }
    ??? 11 :??????????return instance;
    ??? 12 :????? }
    ??? 13 :? }

    用JavaWorld上對應文章的標題來評論這種做法就是smart, but broken。來看原因:

    Java編譯器為了提高程序性能會進行指令調度,CPU在執行指令時同樣出于性能會亂序執行(至少現在用的大多數通用處理器都是out-of-order的),另外cache的存在也會改變數據回寫內存時的順序[2]。JMM(Java Memory Model, 見[1])指出所有的這些優化都是允許的,只要運行結果和嚴格按順序執行所得的結果一樣即可。

    Java假設每個線程都跑在自己的處理器上,享有自己的內存,和共享的主存交互。注意即使在單核上這種模型也是有意義的,考慮到cache和寄存器會保存部分臨時變量。理論上每個線程修改自己的內存后,必須立即更新對應的主存內容。但是Java設計師們認為這種約束會影響程序性能,他們試著創造了一套讓程序跑得更快、但又保證線程之間的交互與預期一致的內存模型。

    synchronized關鍵字便是其中一把利器。事實上,synchronized塊的實現和Linux中的信號量(semaphore)還是有區別的,前者過程中鎖的獲得和釋放都會都會引發一次Memory Barrier來強制線程本地內存和主存之間的同步。通過這個機制,Java中的同步機制保證了synchronized塊中指令的原子性(atomic)。

    好了,回過頭來看DCL問題。看起來訪問一個未同步的instance字段不會產生什么問題,我們再次來假設一個場景:

    線程一進入同步塊,執行instance = new Singleton(); 線程二剛開始執行getResource();

    按照順序的話,接下來應該執行的步驟是 1) 分配新的Singleton對象的內存 2) 調用Singleton的構造器,初始化成員字段 3) instance被賦為指向新的對象的引用。

    前面說過,編譯器或處理器都為了提高性能都有可能進行指令的亂序執行,線程一的真正執行步驟可能是1) 分配內存 2) instance指向新對象 3) 初始化新實例。如果線程二在2完成后3執行前被喚醒,它看到了一個不為null的instance,跳出方法體走了,帶著一個還沒初始化的Singleton對象。

    錯誤發生的一種情形就是這樣,關于更詳細的編譯器指令調度導致的問題,可以參看這個網頁 [4]。

    [3] 中提供了一個編譯器指令調度的證據

    instance = new Singleton(); 這條命令在Symantec JIT中被編譯成

    0206106A?? mov???????? eax,0F97E78h
    0206106F?? call??????? 01F6B210????????????????? ; 分配空間
    02061074?? mov???????? dword ptr [ebp],eax?????? ; EBP中保存了instance的地址

    02061077?? mov???????? ecx,dword ptr [eax]?????? ; 解引用,獲得新的指針地址

    02061079?? mov???????? dword ptr [ecx],100h????? ; 接下來四行是inline后的構造器
    0206107F?? mov???????? dword ptr [ecx+4],200h???
    02061086?? mov???????? dword ptr [ecx+8],400h
    0206108D?? mov???????? dword ptr [ecx+0Ch],0F84030h

    可以看到,賦值完成在初始化之前,而這是JLS允許的。
    ?
    另一種情形是,假設線程一安穩地完成Singleton對象的初始化,退出了同步塊,并同步了和本地內存和主存。線程二來了,看到一個非空的引用,拿走。注意線程二沒有執行一個Read Barrier,因為它根本就沒進后面的同步塊。所以很有可能此時它看到的數據是陳舊的。

    還有很多人根據已知的幾種提出了一個又一個fix的方法,但最終還是出現了更多的問題。可以參閱[3]中的介紹。

    [5]中還說明了即使把instance字段聲明為volatile還是無法避免錯誤的原因。

    由此可見,安全的Singleton的構造一般只有兩種方法,一是在類載入時就創建該實例,二是使用性能較差的synchronized方法。

    (by ZelluX? http://m.tkk7.com/zellux )

    參考資料:

    [1] Java Language Specification, Second Edition, 第17章介紹了Java中線程和內存交互關系的具體細節。
    [2] out-of-order與cache的介紹可以參閱Computer System, A Programmer's Perspective的第四、五章。
    [3] The "Double-Checked Locking is Broken" Declaration, http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
    [4] Synchronization and the Java Memory Model, http://gee.cs.oswego.edu/dl/cpj/jmm.html
    [5] Double-checked locking: Clever, but broken, http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1
    [6] Holub on Patterns, Learning Design Patterns by Looking at Code

    ?


    評論

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-07 23:10 by 王能
    http://www.bt285.cn 這個BT網站,與http://yaonba.com.cn NBA中文網框架也是用singleton

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-08 11:38 by dennis
    請參考volatile關鍵字,在jdk5以上版本,將instance聲明為volatile,DCL是可以的。當然,最好的方案還是采用static holder。這個問題真是討論爛了。

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-08 11:46 by ZelluX
    @dennis
    恩,jdk5已經fix這個bug了。

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-08 23:31 by stanleyxu
    You should use singleton only when it is needed.
    You are right, there are two thread-safe ways to access a singleton object.
    1) Create a static singleton object on initialization. (If your code uses this object very frequently, I recommend you take this method)
    2) Use synchronized or critical section, lock, etc in other languages. (You should avoid use this, unless the object must be created at running time.)

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-09 08:53 by ZelluX
    @stanleyxu
    水木上看到過一個更好的解決方案,利用jvm的ClassLoader機制保證。

    public Single{
    static class Holder{
    static Single inst = new Single();
    }
    static public Single getInstance(){
    return Holder.inst;
    }
    }

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-12 13:46 by
    @ZelluX

    嗯,這種方法在 Effective Java 中有過介紹。

    # re: Singleton模式與雙檢測鎖定(DCL)  回復  更多評論   

    2008-04-16 23:50 by luohandsome
    看上去Java 的同步不比內核簡單
    主站蜘蛛池模板: 国产一区二区三区免费视频| 亚洲色大成网站www久久九| 又粗又黄又猛又爽大片免费| 84pao国产成视频免费播放| 免费无码专区毛片高潮喷水| 亚洲人色大成年网站在线观看| 在线观看亚洲av每日更新| 狠狠久久永久免费观看| 无码乱肉视频免费大全合集| 暖暖在线视频免费视频| 亚洲精品视频免费观看| 在线观看亚洲免费视频| 亚洲最大无码中文字幕| 亚洲成人免费网址| 亚洲av无码成人黄网站在线观看 | 久久亚洲免费视频| 亚洲精品视频在线观看你懂的| 日本媚薬痉挛在线观看免费| 国产情侣激情在线视频免费看| 日韩精品人妻系列无码专区免费 | 亚洲AV人无码综合在线观看| 久久亚洲精品无码播放| 亚洲福利精品电影在线观看| 在线jyzzjyzz免费视频| 成年女性特黄午夜视频免费看| 1000部国产成人免费视频| 亚洲国产精品免费在线观看| 114级毛片免费观看| 91短视频在线免费观看| 国产激情免费视频在线观看| 免费国产在线视频| 三年片在线观看免费大全电影| 久久免费观看国产精品88av| 久久aa毛片免费播放嗯啊| 国产一精品一AV一免费| 日本中文字幕免费高清视频| 暖暖免费在线中文日本| 18女人水真多免费高清毛片| 精品久久8x国产免费观看| 国产免费AV片在线播放唯爱网| 美女视频黄免费亚洲|