Jhonney的專欄

             ----人見人愛
          隨筆 - 49, 文章 - 1, 評論 - 23, 引用 - 0
          數(shù)據(jù)加載中……

          單例的雙檢鎖

           

          前幾天在看一段.NET源代碼的時(shí)候偶爾遇到了Double-checked Locking (雙檢鎖)的一個(gè)使用,于是想到了以前看過的一些資料,寫出來分享一下。

          主要參考:The "Double-Checked Locking is Broken" Delaration (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)

          雙檢鎖是在多線程環(huán)境下很常見的一種實(shí)現(xiàn)singleton模式里lazy initialization的方法。

          先看一下最這個(gè)模式的起源(注:代碼為Java,不過這個(gè)問題適用各種語言,比如C++):

          // Single threaded version
          class Foo {
          private Helper helper = null;
          public Helper getHelper() {
          if (helper == null)
          helper
          = new Helper();
          return helper;
          }
          // other functions and members...
          }

          很容易看出,

          在多線程的情況下,上面的getHelper是不能正確工作的(可能生成多個(gè)helper實(shí)體)。

           

          于是有下面的改進(jìn)代碼:

          // Correct multithreaded version
          class Foo {
          private Helper helper = null;
          public synchronized Helper getHelper() {
          if (helper == null)
          helper
          = new Helper();
          return helper;
          }
          // other functions and members...
          }

          這樣寫程序不會出錯(cuò),因?yàn)檎麄€(gè)getHelper是一個(gè)整體的"critical section",但就是效率很不好,因?yàn)槲覀兊哪康钠鋵?shí)只是在第一個(gè)初始化helper的時(shí)候需要locking(加鎖),而后面取用helper的時(shí)候,根本不需要線程同步。

          于是聰明的人們想出了下面的做法:

          // Broken multithreaded version
          // "Double-Checked Locking" idiom
          class Foo {
          private Helper helper = null;
          public Helper getHelper() {
          if (helper == null)
          synchronized(
          this) {
          if (helper == null)
          helper
          = new Helper();
          }
          return helper;
          }
          // other functions and members...
          }

          思路很簡單,就是我們只需要同步(synchronize)初始化helper的那部分代碼從而使代碼既正確又很有效率。

          這就是所謂的“雙檢鎖”機(jī)制(顧名思義)。

          很可惜,這樣的寫法在很多平臺和優(yōu)化編譯器上是錯(cuò)誤的。

          原因在于:helper = new Helper()這行代碼在不同編譯器上的行為是無法預(yù)知的。一個(gè)優(yōu)化編譯器可以合法地如下實(shí)現(xiàn)helper = new Helper():

          1. helper = 給新的實(shí)體分配內(nèi)存

          2. 調(diào)用helper的構(gòu)造函數(shù)來初始化helper的成員變量

          現(xiàn)在想象一下有線程A和B在調(diào)用getHelper,

          線程A先進(jìn)入,在執(zhí)行到步驟1的時(shí)候被踢出了cpu。然后線程B進(jìn)入,B看到的是helper 已經(jīng)不是null了(內(nèi)存已經(jīng)分配),于是它開始放心地使用helper,但這個(gè)是錯(cuò)誤的,因?yàn)樵谶@一時(shí)刻,helper的成員變量還都是缺省值,A還沒 有來得及執(zhí)行步驟2來完成helper的初始化。

           

          當(dāng)然編譯器也可以這樣實(shí)現(xiàn):

          1. temp = 分配內(nèi)存

          2. 調(diào)用temp的構(gòu)造函數(shù)

          3. helper = temp

          如果編譯器的行為是這樣的話我們似乎就沒有問題了,但事實(shí)卻不是那么簡單,因?yàn)槲覀儫o法知道某個(gè)編譯器具體是怎么做的,因?yàn)樵贘ava的 memory model里對這個(gè)問題沒有定義(C++也一樣),而事實(shí)上有很多編譯器都是用第一種方法(比如symantec的just-in-time compiler),因?yàn)榈谝环N方法看起來更自然。

          在上面的參考文章中還提到了更復(fù)雜的修改方法,不過很可惜,都是錯(cuò)誤的,我這里就略去不介紹了。

          那么有什么解決方案呢?有如下一些:

          1. 如果你的singleton是static的,那你可以將這個(gè)singleton申明為一個(gè)獨(dú)立類的一個(gè)成員變量:

          class HelperSingleton {
          static Helper singleton = new Helper();
          }

          Java的語意會保證:1. lazy initialization, 2. singleton在被調(diào)用前已經(jīng)完全初始化了。

          2. 雙檢鎖對于基礎(chǔ)類型(比如int)適用。很顯然吧,因?yàn)榛A(chǔ)類型沒有調(diào)用構(gòu)造函數(shù)這一步。事實(shí)上,我前面提到的.NET里面的那段代碼就是在一個(gè)int變量上使用雙檢鎖。

          3. 使用explicit memory barrier。這個(gè)我不說了,關(guān)于memory barrier我們可以寫一本小冊子來介紹,有興趣的朋友可以自己查一下資料,上面的參考里也有很多相關(guān)鏈接。

          4. 使用Thread Local Storage。也不介紹了。

          上面的文章還提到了Java在考慮為volatile關(guān)鍵字定義新的語意來解決這個(gè)問題以及雙檢鎖對Java里immutable對象影響,

          不過因 為這篇文章已經(jīng)有些年頭而我也不是Java的專家,所以不太清楚現(xiàn)在的情況怎樣,總之,在遇到雙檢鎖的時(shí)候,需要的朋友應(yīng)該做些必要的調(diào)查來確定自己的代 碼是線程安全的。

          posted on 2007-04-12 21:53 Jhonney 閱讀(2653) 評論(2)  編輯  收藏

          評論

          # re: 單例的雙檢鎖  回復(fù)  更多評論   

          也可以這樣!

          private static Helper helper;
          public static Helper GetHelper()
          {
          //通過“雙檢鎖” 模式支持多線程的應(yīng)用
          if (balancer==null)
          {
          //只有一個(gè)線程可以取得 mutex .
          Mutex mutex = new Mutex();
          mutex.WaitOne();
          if (helper==null)
          {
          helper= new Helper();
          mutex.Close();
          }
          } return helper;
          }
          2011-04-08 15:26 | Tonight

          # re: 單例的雙檢鎖  回復(fù)  更多評論   

          把 IF 里面那balancer 改成 helper ,忘了改了!
          2011-04-08 15:27 | Tonight

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
          博客園   IT新聞   Chat2DB   C++博客   博問  
           
          主站蜘蛛池模板: 天祝| 台北县| 湘乡市| 白水县| 天台县| 井冈山市| 娱乐| 墨竹工卡县| 方正县| 县级市| 揭东县| 平武县| 公主岭市| 逊克县| 吕梁市| 化德县| 伊金霍洛旗| 宜兴市| 旬阳县| 丹棱县| 青冈县| 洛隆县| 英山县| 周宁县| 新巴尔虎右旗| 中西区| 六安市| 通江县| 新沂市| 革吉县| 方山县| 南宁市| 林周县| 呼伦贝尔市| 大竹县| 美姑县| 若尔盖县| 东乡族自治县| 汪清县| 抚顺县| 崇礼县|