溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

DCL和Singleton模式的問題怎么解決

發(fā)布時間:2022-01-10 11:06:28 來源:億速云 閱讀:147 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“DCL和Singleton模式的問題怎么解決”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“DCL和Singleton模式的問題怎么解決”吧!

在多線程的情況下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:  }

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

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

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

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

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

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

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

雙檢測鎖定解決方案

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

    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上對應(yīng)文章的標(biāo)題來評論這種做法就是smart, but broken。來看原因:

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

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

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

雙檢測鎖定的問題

好了,回過頭來看DCL問題??雌饋碓L問一個未同步的instance字段不會產(chǎn)生什么問題,我們再次來假設(shè)一個場景:

線程一進(jìn)入同步塊,執(zhí)行instance = new Singleton(); 線程二剛開始執(zhí)行g(shù)etResource();

按照順序的話,接下來應(yīng)該執(zhí)行的步驟是 1) 分配新的Singleton對象的內(nèi)存 2) 調(diào)用Singleton的構(gòu)造器,初始化成員字段 3) instance被賦為指向新的對象的引用。

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

錯誤發(fā)生的一種情形就是這樣,關(guān)于更詳細(xì)的編譯器指令調(diào)度導(dǎo)致的問題,可以參看這個網(wǎng)頁 [4]。

[3] 中提供了一個編譯器指令調(diào)度的證據(jù)

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后的構(gòu)造器  0206107F   mov         dword ptr [ecx+4],200h      02061086   mov         dword ptr [ecx+8],400h  0206108D   mov         dword ptr [ecx+0Ch],0F84030h

可以看到,賦值完成在初始化之前,而這是JLS允許的。

另一種情形是,假設(shè)線程一安穩(wěn)地完成Singleton對象的初始化,退出了同步塊,并同步了和本地內(nèi)存和主存。線程二來了,看到一個非空的引用,拿走。注意線程二沒有執(zhí)行一個Read Barrier,因為它根本就沒進(jìn)后面的同步塊。所以很有可能此時它看到的數(shù)據(jù)是陳舊的。

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

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

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

到此,相信大家對“DCL和Singleton模式的問題怎么解決”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI