級別:中級PeterHaggar,高級軟件工程師,IBM2004年5月01日所有的編程語言都有一些共用的習(xí)語。了解和使用一些習(xí)語很有用,程序員們花費(fèi)寶貴的時(shí)間來創(chuàng)建、學(xué)習(xí)和實(shí)現(xiàn)這些習(xí)語。問題是,稍后經(jīng)過證明,一些習(xí)語并不完全如其所聲稱的那樣,或者僅僅是與描述的功能不符。在Java編程語言中,雙重檢查鎖定就是這樣的一個(gè)絕不應(yīng)該使用的習(xí)語。在本文中,PeterHaggar" />

欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

雙重檢查鎖定及單例模式

系統(tǒng) 2301 0

雙重檢查鎖定及單例模式

全面理解這一失效的編程習(xí)語


<!----><!----><!---->

級別: 中級

Peter Haggar , 高級軟件工程師, IBM

2004 年 5 月 01 日

所 有的編程語言都有一些共用的習(xí)語。了解和使用一些習(xí)語很有用,程序員們花費(fèi)寶貴的時(shí)間來創(chuàng)建、學(xué)習(xí)和實(shí)現(xiàn)這些習(xí)語。問題是,稍后經(jīng)過證明,一些習(xí)語并不完 全如其所聲稱的那樣,或者僅僅是與描述的功能不符。在 Java 編程語言中,雙重檢查鎖定就是這樣的一個(gè)絕不應(yīng)該使用的習(xí)語。在本文中,Peter Haggar 介紹了雙重檢查鎖定習(xí)語的淵源,開發(fā)它的原因和它失效的原因。
<!----><!----> <!---->

編輯注 :本文在針對 Java 5.0 修訂前參考了 Java 內(nèi)存模型;關(guān)于內(nèi)存排序的描述也許不再正確。盡管如此,在新的內(nèi)存模型中,雙重檢查鎖定習(xí)語仍舊是無效的。

單 例創(chuàng)建模式是一個(gè)通用的編程習(xí)語。和多線程一起使用時(shí),必需使用某種類型的同步。在努力創(chuàng)建更有效的代碼時(shí),Java 程序員們創(chuàng)建了雙重檢查鎖定習(xí)語,將其和單例創(chuàng)建模式一起使用,從而限制同步代碼量。然而,由于一些不太常見的 Java 內(nèi)存模型細(xì)節(jié)的原因,并不能保證這個(gè)雙重檢查鎖定習(xí)語有效。它偶爾會(huì)失敗,而不是總失敗。此外,它失敗的原因并不明顯,還包含 Java 內(nèi)存模型的一些隱秘細(xì)節(jié)。這些事實(shí)將導(dǎo)致代碼失敗,原因是雙重檢查鎖定難于跟蹤。在本文余下的部分里,我們將詳細(xì)介紹雙重檢查鎖定習(xí)語,從而理解它在何處 失效。

單例創(chuàng)建習(xí)語

要理解雙重檢查鎖定習(xí)語是從哪里起源的,就必須理解通用單例創(chuàng)建習(xí)語,如清單 1 中的闡釋:


清單 1. 單例創(chuàng)建習(xí)語
                    				

import java.util.*;
class Singleton
{
  private static Singleton instance;
  private Vector v;
  private boolean inUse;

  private Singleton()
  {
    v = new Vector();
    v.addElement(new Object());
    inUse = true;
  }

  public static Singleton getInstance()
  {
    if (instance == null)          //1
      instance = new Singleton();  //2
    return instance;               //3
  }
}

                  

此類的設(shè)計(jì)確保只創(chuàng)建一個(gè) Singleton 對象。構(gòu)造函數(shù)被聲明為 private getInstance() 方法只創(chuàng)建一個(gè)對象。這個(gè)實(shí)現(xiàn)適合于單線程程序。然而,當(dāng)引入多線程時(shí),就必須通過同步來保護(hù) getInstance() 方法。如果不保護(hù) getInstance() 方法,則可能返回 Singleton 對象的兩個(gè)不同的實(shí)例。假設(shè)兩個(gè)線程并發(fā)調(diào)用 getInstance() 方法并且按以下順序執(zhí)行調(diào)用:

  1. 線程 1 調(diào)用 getInstance() 方法并決定 instance 在 //1 處為 null

  2. 線程 1 進(jìn)入 if 代碼塊,但在執(zhí)行 //2 處的代碼行時(shí)被線程 2 預(yù)占。

  3. 線程 2 調(diào)用 getInstance() 方法并在 //1 處決定 instance null

  4. 線程 2 進(jìn)入 if 代碼塊并創(chuàng)建一個(gè)新的 Singleton 對象并在 //2 處將變量 instance 分配給這個(gè)新對象。

  5. 線程 2 在 //3 處返回 Singleton 對象引用。

  6. 線程 2 被線程 1 預(yù)占。

  7. 線程 1 在它停止的地方啟動(dòng),并執(zhí)行 //2 代碼行,這導(dǎo)致創(chuàng)建另一個(gè) Singleton 對象。

  8. 線程 1 在 //3 處返回這個(gè)對象。

結(jié)果是 getInstance() 方法創(chuàng)建了兩個(gè) Singleton 對象,而它本該只創(chuàng)建一個(gè)對象。通過同步 getInstance() 方法從而在同一時(shí)間只允許一個(gè)線程執(zhí)行代碼,這個(gè)問題得以改正,如清單 2 所示:


清單 2. 線程安全的 getInstance() 方法
                    				

public static synchronized Singleton getInstance()
{
  if (instance == null)          //1
    instance = new Singleton();  //2
  return instance;               //3
}

                  

清單 2 中的代碼針對多線程訪問 getInstance() 方法運(yùn)行得很好。然而,當(dāng)分析這段代碼時(shí),您會(huì)意識(shí)到只有在第一次調(diào)用方法時(shí)才需要同步。由于只有第一次調(diào)用執(zhí)行了 //2 處的代碼,而只有此行代碼需要同步,因此就無需對后續(xù)調(diào)用使用同步。所有其他調(diào)用用于決定 instance 是非 null 的,并將其返回。多線程能夠安全并發(fā)地執(zhí)行除第一次調(diào)用外的所有調(diào)用。盡管如此,由于該方法是 synchronized 的,需要為該方法的每一次調(diào)用付出同步的代價(jià),即使只有第一次調(diào)用需要同步。

為使此方法更為有效,一個(gè)被稱為雙重檢查鎖定的習(xí)語就應(yīng)運(yùn)而生了。這個(gè)想法是為了避免對除第一次調(diào)用外的所有調(diào)用都實(shí)行同步的昂貴代價(jià)。同步的代價(jià)在不同的 JVM 間是不同的。在早期,代價(jià)相當(dāng)高。隨著更高級的 JVM 的出現(xiàn),同步的代價(jià)降低了,但出入 synchronized 方法或塊仍然有性能損失。不考慮 JVM 技術(shù)的進(jìn)步,程序員們絕不想不必要地浪費(fèi)處理時(shí)間。

因?yàn)橹挥星鍐?2 中的 //2 行需要同步,我們可以只將其包裝到一個(gè)同步塊中,如清單 3 所示:


清單 3. getInstance() 方法
                    				

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

                  

清單 3 中的代碼展示了用多線程加以說明的和清單 1 相同的問題。當(dāng) instance null 時(shí),兩個(gè)線程可以并發(fā)地進(jìn)入 if 語句內(nèi)部。然后,一個(gè)線程進(jìn)入 synchronized 塊來初始化 instance ,而另一個(gè)線程則被阻斷。當(dāng)?shù)谝粋€(gè)線程退出 synchronized 塊時(shí),等待著的線程進(jìn)入并創(chuàng)建另一個(gè) Singleton 對象。注意:當(dāng)?shù)诙€(gè)線程進(jìn)入 synchronized 塊時(shí),它并沒有檢查 instance 是否非 null





回頁首


雙重檢查鎖定

為處理清單 3 中的問題,我們需要對 instance 進(jìn)行第二次檢查。這就是“雙重檢查鎖定”名稱的由來。將雙重檢查鎖定習(xí)語應(yīng)用到清單 3 的結(jié)果就是清單 4 。


清單 4. 雙重檢查鎖定示例
                    				

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}

                  

雙重檢查鎖定背后的理論是:在 //2 處的第二次檢查使(如清單 3 中那樣)創(chuàng)建兩個(gè)不同的 Singleton 對象成為不可能。假設(shè)有下列事件序列:

  1. 線程 1 進(jìn)入 getInstance() 方法。

  2. 由于 instance null ,線程 1 在 //1 處進(jìn)入 synchronized 塊。

  3. 線程 1 被線程 2 預(yù)占。

  4. 線程 2 進(jìn)入 getInstance() 方法。

  5. 由于 instance 仍舊為 null ,線程 2 試圖獲取 //1 處的鎖。然而,由于線程 1 持有該鎖,線程 2 在 //1 處阻塞。

  6. 線程 2 被線程 1 預(yù)占。

  7. 線程 1 執(zhí)行,由于在 //2 處實(shí)例仍舊為 null ,線程 1 還創(chuàng)建一個(gè) Singleton 對象并將其引用賦值給 instance

  8. 線程 1 退出 synchronized 塊并從 getInstance() 方法返回實(shí)例。

  9. 線程 1 被線程 2 預(yù)占。

  10. 線程 2 獲取 //1 處的鎖并檢查 instance 是否為 null

  11. 由于 instance 是非 null 的,并沒有創(chuàng)建第二個(gè) Singleton 對象,由線程 1 創(chuàng)建的對象被返回。

雙重檢查鎖定背后的理論是完美的。不幸地是,現(xiàn)實(shí)完全不同。雙重檢查鎖定的問題是:并不能保證它會(huì)在單處理器或多處理器計(jì)算機(jī)上順利運(yùn)行。

雙重檢查鎖定失敗的問題并不歸咎于 JVM 中的實(shí)現(xiàn) bug,而是歸咎于 Java 平臺(tái)內(nèi)存模型。內(nèi)存模型允許所謂的“無序?qū)懭搿保@也是這些習(xí)語失敗的一個(gè)主要原因。





回頁首


無序?qū)懭?

為解釋該問題,需要重新考察上述清單 4 中的 //3 行。此行代碼創(chuàng)建了一個(gè) Singleton 對象并初始化變量 instance 來引用此對象。這行代碼的問題是:在 Singleton 構(gòu)造函數(shù)體執(zhí)行之前,變量 instance 可能成為非 null 的。

什么?這一說法可能讓您始料未及,但事實(shí)確實(shí)如此。在解釋這個(gè)現(xiàn)象如何發(fā)生前,請先暫時(shí)接受這一事實(shí),我們先來考察一下雙重檢查鎖定是如何被破壞的。假設(shè)清單 4 中代碼執(zhí)行以下事件序列:

  1. 線程 1 進(jìn)入 getInstance() 方法。

  2. 由于 instance null ,線程 1 在 //1 處進(jìn)入 synchronized 塊。

  3. 線程 1 前進(jìn)到 //3 處,但在構(gòu)造函數(shù)執(zhí)行 之前 ,使實(shí)例成為非 null

  4. 線程 1 被線程 2 預(yù)占。

  5. 線程 2 檢查實(shí)例是否為 null 。因?yàn)閷?shí)例不為 null,線程 2 將 instance 引用返回給一個(gè)構(gòu)造完整但部分初始化了的 Singleton 對象。

  6. 線程 2 被線程 1 預(yù)占。

  7. 線程 1 通過運(yùn)行 Singleton 對象的構(gòu)造函數(shù)并將引用返回給它,來完成對該對象的初始化。

此事件序列發(fā)生在線程 2 返回一個(gè)尚未執(zhí)行構(gòu)造函數(shù)的對象的時(shí)候。

為展示此事件的發(fā)生情況,假設(shè)為代碼行 instance =new Singleton(); 執(zhí)行了下列偽代碼: instance =new Singleton();

                    mem = allocate();             //Allocate memory for Singleton object.
instance = mem;               //Note that instance is now non-null, but
                              //has not been initialized.
ctorSingleton(instance);      //Invoke constructor for Singleton passing
                              //instance.

                  

這段偽代碼不僅是可能的,而且是一些 JIT 編譯器上真實(shí)發(fā)生的。執(zhí)行的順序是顛倒的,但鑒于當(dāng)前的內(nèi)存模型,這也是允許發(fā)生的。JIT 編譯器的這一行為使雙重檢查鎖定的問題只不過是一次學(xué)術(shù)實(shí)踐而已。

為說明這一情況,假設(shè)有清單 5 中的代碼。它包含一個(gè)剝離版的 getInstance() 方法。我已經(jīng)刪除了“雙重檢查性”以簡化我們對生成的匯編代碼(清單 6)的回顧。我們只關(guān)心 JIT 編譯器如何編譯 instance=new Singleton(); 代碼。此外,我提供了一個(gè)簡單的構(gòu)造函數(shù)來明確說明匯編代碼中該構(gòu)造函數(shù)的運(yùn)行情況。


清單 5. 用于演示無序?qū)懭氲膯卫?
                    				

class Singleton
{
  private static Singleton instance;
  private boolean inUse;
  private int val;  

  private Singleton()
  {
    inUse = true;
    val = 5;
  }
  public static Singleton getInstance()
  {
    if (instance == null)
      instance = new Singleton();
    return instance;
  }
}

                  

清單 6 包含由 Sun JDK 1.2.1 JIT 編譯器為清單 5 中的 getInstance() 方法體生成的匯編代碼。


清單 6. 由清單 5 中的代碼生成的匯編代碼
                    				

;asm code generated for getInstance
054D20B0   mov         eax,[049388C8]      ;load instance ref
054D20B5   test        eax,eax             ;test for null
054D20B7   jne         054D20D7
054D20B9   mov         eax,14C0988h
054D20BE   call        503EF8F0            ;allocate memory
054D20C3   mov         [049388C8],eax      ;store pointer in 
                                           ;instance ref. instance  
                                           ;non-null and ctor
                                           ;has not run
054D20C8   mov         ecx,dword ptr [eax] 
054D20CA   mov         dword ptr [ecx],1   ;inline ctor - inUse=true;
054D20D0   mov         dword ptr [ecx+4],5 ;inline ctor - val=5;
054D20D7   mov         ebx,dword ptr ds:[49388C8h]
054D20DD   jmp         054D20B0

                  

注: 為引用下列說明中的匯編代碼行,我將引用指令地址的最后兩個(gè)值,因?yàn)樗鼈兌家? 054D20 開頭。例如, B5 代表 test eax,eax

匯編代碼是通過運(yùn)行一個(gè)在無限循環(huán)中調(diào)用 getInstance() 方法的測試程序來生成的。程序運(yùn)行時(shí),請運(yùn)行 Microsoft Visual C++ 調(diào)試器并將其附到表示測試程序的 Java 進(jìn)程中。然后,中斷執(zhí)行并找到表示該無限循環(huán)的匯編代碼。

B0 B5 處的前兩行匯編代碼將 instance 引用從內(nèi)存位置 049388C8 加載至 eax 中,并進(jìn)行 null 檢查。這跟清單 5 中的 getInstance() 方法的第一行代碼相對應(yīng)。第一次調(diào)用此方法時(shí), instance null ,代碼執(zhí)行到 B9 BE 處的代碼為 Singleton 對象從堆中分配內(nèi)存,并將一個(gè)指向該塊內(nèi)存的指針存儲(chǔ)到 eax 中。下一行代碼, C3 ,獲取 eax 中的指針并將其存儲(chǔ)回內(nèi)存位置為 049388C8 的實(shí)例引用。結(jié)果是, instance 現(xiàn)在為非 null 并引用一個(gè)有效的 Singleton 對象。然而,此對象的構(gòu)造函數(shù)尚未運(yùn)行,這恰是破壞雙重檢查鎖定的情況。然后,在 C8 行處, instance 指針被解除引用并存儲(chǔ)到 ecx CA D0 行表示內(nèi)聯(lián)的構(gòu)造函數(shù),該構(gòu)造函數(shù)將值 true 5 存儲(chǔ)到 Singleton 對象。如果此代碼在執(zhí)行 C3 行后且在完成該構(gòu)造函數(shù)前被另一個(gè)線程中斷,則雙重檢查鎖定就會(huì)失敗。

不是所有的 JIT 編譯器都生成如上代碼。一些生成了代碼,從而只在構(gòu)造函數(shù)執(zhí)行后使 instance 成為非 null 。 針對 Java 技術(shù)的 IBM SDK 1.3 版和 Sun JDK 1.3 都生成這樣的代碼。然而,這并不意味著應(yīng)該在這些實(shí)例中使用雙重檢查鎖定。該習(xí)語失敗還有一些其他原因。此外,您并不總能知道代碼會(huì)在哪些 JVM 上運(yùn)行,而 JIT 編譯器總是會(huì)發(fā)生變化,從而生成破壞此習(xí)語的代碼。





回頁首


雙重檢查鎖定:獲取兩個(gè)

考慮到當(dāng)前的雙重檢查鎖定不起作用,我加入了另一個(gè)版本的代碼,如清單 7 所示,從而防止您剛才看到的無序?qū)懭雴栴}。


清單 7. 解決無序?qū)懭雴栴}的嘗試
                    				

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          inst = new Singleton();        //4
        }
        instance = inst;                 //5
      }
    }
  }
  return instance;
}

                  

看著清單 7 中的代碼,您應(yīng)該意識(shí)到事情變得有點(diǎn)荒謬。請記住,創(chuàng)建雙重檢查鎖定是為了避免對簡單的三行 getInstance() 方法實(shí)現(xiàn)同步。清單 7 中的代碼變得難于控制。另外,該代碼沒有解決問題。仔細(xì)檢查可獲悉原因。

此代碼試圖避免無序?qū)懭雴栴}。它試圖通過引入局部變量 inst 和第二個(gè) synchronized 塊來解決這一問題。該理論實(shí)現(xiàn)如下:

  1. 線程 1 進(jìn)入 getInstance() 方法。

  2. 由于 instance null ,線程 1 在 //1 處進(jìn)入第一個(gè) synchronized 塊。

  3. 局部變量 inst 獲取 instance 的值,該值在 //2 處為 null

  4. 由于 inst null ,線程 1 在 //3 處進(jìn)入第二個(gè) synchronized 塊。

  5. 線程 1 然后開始執(zhí)行 //4 處的代碼,同時(shí)使 inst 為非 null ,但在 Singleton 的構(gòu)造函數(shù)執(zhí)行前。(這就是我們剛才看到的無序?qū)懭雴栴}。)

  6. 線程 1 被線程 2 預(yù)占。

  7. 線程 2 進(jìn)入 getInstance() 方法。

  8. 由于 instance null ,線程 2 試圖在 //1 處進(jìn)入第一個(gè) synchronized 塊。由于線程 1 目前持有此鎖,線程 2 被阻斷。

  9. 線程 1 然后完成 //4 處的執(zhí)行。

  10. 線程 1 然后將一個(gè)構(gòu)造完整的 Singleton 對象在 //5 處賦值給變量 instance ,并退出這兩個(gè) synchronized 塊。

  11. 線程 1 返回 instance

  12. 然后執(zhí)行線程 2 并在 //2 處將 instance 賦值給 inst

  13. 線程 2 發(fā)現(xiàn) instance 為非 null ,將其返回。

這里的關(guān)鍵行是 //5。此行應(yīng)該確保 instance 只為 null 或引用一個(gè)構(gòu)造完整的 Singleton 對象。該問題發(fā)生在理論和實(shí)際彼此背道而馳的情況下。

由于當(dāng)前內(nèi)存模型的定義,清單 7 中的代碼無效。Java 語言規(guī)范(Java Language Specification,JLS)要求不能將 synchronized 塊中的代碼移出來。但是,并沒有說不能將 synchronized 塊外面的代碼移 synchronized 塊中。

JIT 編譯器會(huì)在這里看到一個(gè)優(yōu)化的機(jī)會(huì)。此優(yōu)化會(huì)刪除 //4 和 //5 處的代碼,組合并且生成清單 8 中所示的代碼。


清單 8. 從清單 7 中優(yōu)化來的代碼。
                    				

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          //inst = new Singleton();      //4
          instance = new Singleton();               
        }
        //instance = inst;               //5
      }
    }
  }
  return instance;
}

                  

如果進(jìn)行此項(xiàng)優(yōu)化,您將同樣遇到我們之前討論過的無序?qū)懭雴栴}。





回頁首


用 volatile 聲明每一個(gè)變量怎么樣?

另一個(gè)想法是針對變量 inst 以及 instance 使用關(guān)鍵字 volatile 。根據(jù) JLS(參見 參考資料 ),聲明成 volatile 的變量被認(rèn)為是順序一致的,即,不是重新排序的。但是試圖使用 volatile 來修正雙重檢查鎖定的問題,會(huì)產(chǎn)生以下兩個(gè)問題:

  • 這里的問題不是有關(guān)順序一致性的,而是代碼被移動(dòng)了,不是重新排序。

  • 即使考慮了順序一致性,大多數(shù)的 JVM 也沒有正確地實(shí)現(xiàn) volatile

第二點(diǎn)值得展開討論。假設(shè)有清單 9 中的代碼:


清單 9. 使用了 volatile 的順序一致性
                    				

class test
{
  private volatile boolean stop = false;
  private volatile int num = 0;

  public void foo()
  {
    num = 100;    //This can happen second
    stop = true;  //This can happen first
    //...
  }

  public void bar()
  {
    if (stop)
      num += num;  //num can == 0!
  }
  //...
}

                  

根據(jù) JLS,由于 stop num 被聲明為 volatile ,它們應(yīng)該順序一致。這意味著如果 stop 曾經(jīng)是 true num 一定曾被設(shè)置成 100 。盡管如此,因?yàn)樵S多 JVM 沒有實(shí)現(xiàn) volatile 的順序一致性功能,您就不能依賴此行為。因此,如果線程 1 調(diào)用 foo 并且線程 2 并發(fā)地調(diào)用 bar ,則線程 1 可能在 num 被設(shè)置成為 100 之前將 stop 設(shè)置成 true 。這將導(dǎo)致線程見到 stop true ,而 num 仍被設(shè)置成 0 。使用 volatile 和 64 位變量的原子數(shù)還有另外一些問題,但這已超出了本文的討論范圍。有關(guān)此主題的更多信息,請參閱 參考資料





回頁首


解決方案

底線就是:無論以何種形式,都不應(yīng)使用雙重檢查鎖定,因?yàn)槟荒鼙WC它在任何 JVM 實(shí)現(xiàn)上都能順利運(yùn)行。JSR-133 是有關(guān)內(nèi)存模型尋址問題的,盡管如此,新的內(nèi)存模型也不會(huì)支持雙重檢查鎖定。因此,您有兩種選擇:

  • 接受如清單 2 中所示的 getInstance() 方法的同步。

  • 放棄同步,而使用一個(gè) static 字段。

選擇項(xiàng) 2 如清單 10 中所示


清單 10. 使用 static 字段的單例實(shí)現(xiàn)
                    				

class Singleton
{
  private Vector v;
  private boolean inUse;
  private static Singleton instance = new Singleton();

  private Singleton()
  {
    v = new Vector();
    inUse = true;
    //...
  }

  public static Singleton getInstance()
  {
    return instance;
  }
}

                  

清單 10 的代碼沒有使用同步,并且確保調(diào)用 static getInstance() 方法時(shí)才創(chuàng)建 Singleton 。如果您的目標(biāo)是消除同步,則這將是一個(gè)很好的選擇。





回頁首


String 不是不變的

鑒于無序?qū)懭牒鸵迷跇?gòu)造函數(shù)執(zhí)行前變成非 null 的問題,您可能會(huì)考慮 String 類。假設(shè)有下列代碼:

                    private String str;
//...
str = new String("hello");

                  

String 類應(yīng)該是不變的。盡管如此,鑒于我們之前討論的無序?qū)懭雴栴},那會(huì)在這里導(dǎo)致問題嗎?答案是肯定的。考慮兩個(gè)線程訪問 String str 。一個(gè)線程能看見 str 引用一個(gè) String 對象,在該對象中構(gòu)造函數(shù)尚未運(yùn)行。事實(shí)上,清單 11 包含展示這種情況發(fā)生的代碼。注意,這個(gè)代碼僅在我測試用的舊版 JVM 上會(huì)失敗。IBM 1.3 和 Sun 1.3 JVM 都會(huì)如期生成不變的 String


清單 11. 可變 String 的例子
                    				

class StringCreator extends Thread
{
  MutableString ms;
  public StringCreator(MutableString muts)
  {
    ms = muts;
  }
  public void run()
  {
    while(true)
      ms.str = new String("hello");          //1
  }
}
class StringReader extends Thread
{
  MutableString ms;
  public StringReader(MutableString muts)
  {
    ms = muts;
  }
  public void run()
  {
    while(true)
    {
      if (!(ms.str.equals("hello")))         //2
      {
        System.out.println("String is not immutable!");
        break;
      }
    }
  }
}
class MutableString
{
  public String str;                         //3
  public static void main(String args[])
  {
    MutableString ms = new MutableString();  //4
    new StringCreator(ms).start();           //5
    new StringReader(ms).start();            //6
  }
}

                  

此代碼在 //4 處創(chuàng)建一個(gè) MutableString 類,它包含了一個(gè) String 引用,此引用由 //3 處的兩個(gè)線程共享。在行 //5 和 //6 處,在兩個(gè)分開的線程上創(chuàng)建了兩個(gè)對象 StringCreator StringReader 。傳入一個(gè) MutableString 對象的引用。 StringCreator 類進(jìn)入到一個(gè)無限循環(huán)中并且使用值“hello”在 //1 處創(chuàng)建 String 對象。 StringReader 也進(jìn)入到一個(gè)無限循環(huán)中,并且在 //2 處檢查當(dāng)前的 String 對象的值是不是 “hello”。如果不行, StringReader 線程打印出一條消息并停止。如果 String 類是不變的,則從此程序應(yīng)當(dāng)看不到任何輸出。如果發(fā)生了無序?qū)懭雴栴},則使 StringReader 看到 str 引用的惟一方法絕不是值為“hello”的 String 對象。

在舊版的 JVM 如 Sun JDK 1.2.1 上運(yùn)行此代碼會(huì)導(dǎo)致無序?qū)懭雴栴}。并因此導(dǎo)致一個(gè)非不變的 String





回頁首


結(jié)束語

為避免單例中代價(jià)高昂的同步,程序員非常聰明地發(fā)明了雙重檢查鎖定習(xí)語。不幸的是,鑒于當(dāng)前的內(nèi)存模型的原因,該習(xí)語尚未得到廣泛使用,就明顯成為了一種 不安全的編程結(jié)構(gòu)。重定義脆弱的內(nèi)存模型這一領(lǐng)域的工作正在進(jìn)行中。盡管如此,即使是在新提議的內(nèi)存模型中,雙重檢查鎖定也是無效的。對此問題最佳的解決 方案是接受同步或者使用一個(gè) static field



參考資料



關(guān)于作者

Peter Haggar 是 IBM 在北卡羅來納州的 Research Triangle Park 的一名高級軟件工程師,他還是 Practical Java Programming Language Guide (Addison-Wesley 出版)一書的作者。此外,他還發(fā)表了很多篇關(guān)于 Java 編程的文章。他有著廣泛的編程經(jīng)驗(yàn),曾致力于開發(fā)工具、類庫和操作系統(tǒng)相關(guān)的工作。Peter 在 IBM 致力于研究新興 Internet 技術(shù),目前主要從事高性能 Web 服務(wù)方面的工作。Peter 經(jīng)常在很多行業(yè)會(huì)議上作為技術(shù)發(fā)言人就 Java 技術(shù)發(fā)表言論。他已經(jīng)為 IBM 工作了 14 年多,并獲得了 Clarkson University 的計(jì)算機(jī)科學(xué)學(xué)士學(xué)位。您可以通過 haggar@us.ibm.com 與他聯(lián)系。

雙重檢查鎖定及單例模式


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 天天看天天爽天天摸天天添 | 夜夜春精品视频 | 国产黄的网站免费 | 午夜精品 | 日日摸夜夜添夜夜添破第一 | 欧美视频第一区 | 一级片在线免费观看视频 | 在线观看国产 | 亚洲激情视频网 | 久久精品这里是免费国产 | 光根电影院 | 日本一区二区精品视频 | 在线视频a | 男女生性毛片免费观看 | 无码免费人妻A片AAA毛片一区 | 国产精品成人一区二区 | 亚洲国产精品久久久久秋霞蜜臀 | 九九精品视频一区二区三区 | 国产精品区一区二区三 | 九九热中文字幕 | 公么吃奶满足了我苏媚 | 久久精品免费一区二区三区 | 一级a毛片免费观看久久精品 | www.伊人网 | 成人精品鲁一区一区二区 | 日韩在线亚洲 | 久久视频免费 | 久久只有这里有精品 | 91精品免费观看 | 一级片在线播放 | 日韩午夜电影 | 亚洲精品久久久中文字幕 | 色秀视频在线观看全部 | 国产网站在线播放 | 亚洲视频一区二区三区 | 人人澡人人澡人人澡 | 欧美日韩一二区 | 欧美精品一区二区三区蜜桃视频 | 日日天天 | 国产精品久久人妻无码网站一区无 | 久久夜色精品国产 |