DANCE WITH JAVA

          開(kāi)發(fā)出高質(zhì)量的系統(tǒng)

          常用鏈接

          統(tǒng)計(jì)

          積分與排名

          好友之家

          最新評(píng)論

          Java中的模式 --單態(tài) (部分翻譯 double-checked locking break)

          單態(tài)定義:
          Singleton模式主要作用是保證在Java應(yīng)用程序中,一個(gè)類Class只有一個(gè)實(shí)例存在。

          Singleton模式就為我們提供了這樣實(shí)現(xiàn)的可能。使用Singleton的好處還在于可以節(jié)省內(nèi)存,因?yàn)樗拗屏?br>實(shí)例的個(gè)數(shù),有利于Java垃圾回收(garbage collection)。

          使用Singleton注意事項(xiàng):
          有時(shí)在某些情況下,使用Singleton并不能達(dá)到Singleton的目的,如有多個(gè)Singleton對(duì)象同時(shí)被不同的類
          裝入器裝載;在EJB這樣的分布式系統(tǒng)中使用也要注意這種情況,因?yàn)镋JB是跨服務(wù)器,跨JVM的

          單態(tài)模式的演化:
          單態(tài)模式是個(gè)簡(jiǎn)單的模式,但是這個(gè)簡(jiǎn)單的模式也有很多復(fù)雜的東西。

          (注意:在這里補(bǔ)充一下,現(xiàn)在單態(tài)模式其實(shí)有一個(gè)寫(xiě)法是不錯(cuò)的見(jiàn)這里:http://www.aygfsteel.com/dreamstone/archive/2007/02/27/101000.html,但還是建議看完這篇文章,因?yàn)榻忉尩氖虑槭遣灰粯拥模@里說(shuō)的是為什么double-checked不能使用.)
          一,首先最簡(jiǎn)單的單態(tài)模式,單態(tài)模式1
          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
            }
          }
          這個(gè)單態(tài)模式是不安全的,為什么說(shuō)呢 ?因?yàn)闆](méi)考慮多線程,如下情況
          Thread 1 調(diào)用getInstance() 方法,并且判斷instance是null,然後進(jìn)入if模塊,
          在實(shí)例化instance之前,
          Thread 2搶占了Thread 1的cpu
          Thread 2 調(diào)用getInstance() 方法,并且判斷instance是null,然後進(jìn)入if模塊,
          Thread 2 實(shí)例化instance 完成,返回
          Thread 1 再次實(shí)例化instance
          這個(gè)單態(tài)已經(jīng)不在是單態(tài)

          二,為了解決剛才的問(wèn)題:?jiǎn)螒B(tài)模式2
          public static synchronized Singleton getInstance()
          {
            if (instance == null)          //1
              instance = new Singleton();  //2
            return instance;               //3
          }
          采用同步來(lái)解決,這種方式解決了問(wèn)題,但是仔細(xì)分析
          正常的情況下只有第一次時(shí)候,進(jìn)入對(duì)象的實(shí)例化,須要同步,
          其它時(shí)候都是直接返回已經(jīng)實(shí)例化好的instance不須要同步,
          大家都知到在一個(gè)多線程的程序中,如果同步的消耗是很大的,很容易造成瓶頸

          三,為了解決上邊的問(wèn)題:?jiǎn)螒B(tài)模式3,加入同步
          public static Singleton getInstance()
          {
            if (instance == null)
            {
              synchronized(Singleton.class) {
                instance = new Singleton();
              }
            }
            return instance;
          }
          同步改成塊同步,而不使用函數(shù)同步,但是仔細(xì)分析,
          又回到了模式一的狀態(tài),再多線程的時(shí)候根本沒(méi)有解決問(wèn)題

          四,為了對(duì)應(yīng)上邊的問(wèn)題:?jiǎn)螒B(tài)模式4,也就是很多人采用的Double-checked locking
          public static Singleton getInstance()
          {
            if (instance == null)
            {
              synchronized(Singleton.class) {  //1
                if (instance == null)          //2
                  instance = new Singleton();  //3
              }
            }
            return instance;
          }
          這樣,模式一中提到的問(wèn)題解決了。不會(huì)出現(xiàn)多次實(shí)例化的現(xiàn)象
          當(dāng)?shù)谝淮芜M(jìn)入的時(shí)候,保正實(shí)例化時(shí)候的單態(tài),在實(shí)例化后,多線程訪問(wèn)的時(shí)候直接返回,不須要進(jìn)入同步模塊,
          既實(shí)現(xiàn)了單態(tài),又沒(méi)有損失性能。表面上看我們的問(wèn)題解決了,但是再仔細(xì)分析:
          我們來(lái)假象這中情況:
          Thread 1 :進(jìn)入到//3位置,執(zhí)行new Singleton(),但是在構(gòu)造函數(shù)剛剛開(kāi)始的時(shí)候被Thread2搶占cpu
          Thread 2 :進(jìn)入getInstance(),判斷instance不等于null,返回instance,
          (instance已經(jīng)被new,已經(jīng)分配了內(nèi)存空間,但是沒(méi)有初始化數(shù)據(jù))
          Thread 2 :利用返回的instance做某些操做,失敗或者異常
          Thread 1 :取得cpu初始化完成
          過(guò)程中可能有多個(gè)線程取到了沒(méi)有完成的實(shí)例,并用這個(gè)實(shí)例作出某些操做。
          -----------------------------------------
          出現(xiàn)以上的問(wèn)題是因?yàn)?br>mem = allocate();             //分配內(nèi)存
          instance = mem;               //標(biāo)記instance非空
                                        //未執(zhí)行構(gòu)造函數(shù),thread 2從這里進(jìn)入
          ctorSingleton(instance);      //執(zhí)行構(gòu)造函數(shù)
                                        //返回instance
          ------------------------------------------                             

          五,證明上邊的假想是可能發(fā)生的,字節(jié)碼是用來(lái)分析問(wèn)題的最好的工具,可以利用它來(lái)分析
          下邊一段程序:(為了分析方便,所以漸少了內(nèi)容)
          字節(jié)碼的使用方法見(jiàn)這里,利用字節(jié)碼分析問(wèn)題
          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;
            }
          }
          得到的字節(jié)碼                           
          ;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

          上邊的字節(jié)碼證明,猜想是有可能實(shí)現(xiàn)的

          六:好了,上邊證明Double-checked locking可能出現(xiàn)取出錯(cuò)誤數(shù)據(jù)的情況,那么我們還是可以解決的
          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;
          }
          利用Double-checked locking 兩次同步,中間變量,解決上邊的問(wèn)題。
          (下邊這段話我只能簡(jiǎn)單的理解,翻譯過(guò)來(lái)不好,所以保留原文,list 7是上邊的代碼,list 8是下邊的
          The code in Listing 7 doesn't work because of the current definition of the memory model.
           The Java Language Specification (JLS) demands that code within a synchronized block
           not be moved out of a synchronized block. However, it does not say that
           code not in a synchronized block cannot be moved into a synchronized block.
          A JIT compiler would see an optimization opportunity here.
          This optimization would remove the code at
          //4 and the code at //5, combine it and generate the code shown in Listing 8:)
          ------------------------------------------------- 
          list 8
          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;
          }
          If this optimization takes place, you have the same out-of-order write problem we discussed earlier.
          如果這個(gè)優(yōu)化發(fā)生,將再次發(fā)生上邊提到的問(wèn)題,取得沒(méi)有實(shí)例化完成的數(shù)據(jù)。
          -------------------------------------------------

          以下部分為了避免我翻譯錯(cuò)誤誤導(dǎo)打家,保留原文

          Another idea is to use the keyword volatile for the variables inst and instance.
          According to the JLS (see Resources), variables declared volatile are supposed to
          be sequentially consistent, and therefore, not reordered.
          But two problems occur with trying to use volatile to fix the problem with
          double-checked locking:

          The problem here is not with sequential consistency.
          Code is being moved, not reordered.

          Many JVMs do not implement volatile correctly regarding sequential consistency anyway.
          The second point is worth expanding upon. Consider the code in Listing 9:

          Listing 9. Sequential consistency with 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!
            }
            //...
          }
           
          According to the JLS, because stop and num are declared volatile,
          they should be sequentially consistent. This means that if stop is ever true,
          num must have been set to 100.
          However, because many JVMs do not implement the sequential consistency feature of volatile,
          you cannot count on this behavior.
          Therefore, if thread 1 called foo and thread 2 called bar concurrently,
          thread 1 might set stop to true before num is set to 100.
          This could lead thread 2 to see stop as true, but num still set to 0.
          There are additional problems with volatile and the atomicity of 64-bit variables,
          but this is beyond the scope of this article.
          See Resources for more information on this topic.

          簡(jiǎn)單的理解上邊這段話,使用volatile有可能能解決問(wèn)題,volatile被定義用來(lái)保正一致性,但是很多虛擬機(jī)
          并沒(méi)有很好的實(shí)現(xiàn)volatile,所以使用它也會(huì)存在問(wèn)題。

          最終的解決方案:
           (1),單態(tài)模式2,使用同步方法
           (2),放棄同步,使用一個(gè)靜態(tài)變量,如下
          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;
            }
          }
          但使用靜態(tài)變量也會(huì)存在問(wèn)題,問(wèn)題見(jiàn) 這篇文章

          而且如在文章開(kāi)頭提到的,使用EJB跨服務(wù)器,跨JVM的情況下,單態(tài)更是問(wèn)題

          好了是不是感覺(jué)單態(tài)模式根本沒(méi)法使用了,其實(shí)上邊都是特殊情況,這中特殊情況的出現(xiàn)是有條件的,只要
          根據(jù)你的具體應(yīng)用,回避一些,就能解決問(wèn)題,所以單態(tài)還是可以使用的。但是在使用前慎重,自己考慮好自己
          的情況適合哪種情況。

          參考
          http://www.jdon.com/designpatterns/singleton.htm

          Double-checked locking and the Singleton pattern

          When is a singleton not a singleton?

          posted on 2006-11-04 09:26 dreamstone 閱讀(8690) 評(píng)論(12)  編輯  收藏 所屬分類: 設(shè)計(jì)模式

          評(píng)論

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2006-12-19 14:51 vg

          四,為了對(duì)應(yīng)上邊的問(wèn)題:?jiǎn)螒B(tài)模式4


          -----------------------
          我想請(qǐng)教一下,
          既然被synchronized,一個(gè)線程沒(méi)執(zhí)行完,另一個(gè)線程是無(wú)法進(jìn)入的.
          就不會(huì)存在instance已經(jīng)被new,已經(jīng)分配了內(nèi)存空間,但是沒(méi)有初始化數(shù)據(jù)的情況

          synchronized(Singleton.class) { //1
          if (instance == null) //2
          instance = new Singleton(); //3
          }
            回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2006-12-19 15:38 vg

          看錯(cuò)了,看成double-checked 了  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2006-12-19 21:42 slx

          double-check也是會(huì)出問(wèn)題的,java中的double-check基本不是確定安全的,所以在使用的時(shí)候要格外小心,之所以很多人喜歡double check 是因?yàn)樵O(shè)計(jì)模式一書(shū)中講單態(tài)的時(shí)候是double-check,但是因?yàn)樵O(shè)計(jì)模式是以c++為基礎(chǔ)的,在c++中可以實(shí)現(xiàn)的,java不行,原因見(jiàn)我的文章中寫(xiě)的,是因?yàn)樘摂M機(jī)的實(shí)現(xiàn)原理,如果英語(yǔ)好的話可以看一下寫(xiě)文章時(shí)我參考的幾篇文章。  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2006-12-19 21:51 wueddie

          ”但使用靜態(tài)變量也會(huì)存在問(wèn)題“
          我看了引用的文章,并不能說(shuō)明靜態(tài)變量的方式有問(wèn)題。
          文章只是說(shuō)明了對(duì)象初始化的過(guò)程和要注意的問(wèn)題。  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-02-27 17:39 dreamstone

          to wueddie:
          如果一個(gè)使用方法存在著各種各樣的陷阱,就說(shuō)明它存在問(wèn)題。呵呵。我并不是說(shuō)不能用,而是說(shuō)在用的時(shí)候慎重。
            回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-02-27 17:41 dreamstone

          另外附加一段轉(zhuǎn)載來(lái)的內(nèi)容,轉(zhuǎn)載自:
          http://www.jdon.com/jive/thread.jsp?forum=91&thread=23890&message=14071631#14071631

          Kyle_Yin 寫(xiě)的部分,本來(lái)想只寫(xiě)個(gè)鏈接,不過(guò)從眾多回文中找也不是個(gè)好事情,所以轉(zhuǎn)載過(guò)來(lái)這一段。

          閻宏描述的原因是確切的。

          不僅如此(構(gòu)造函數(shù)與 instance 變量賦值的順序完全取決于編譯器實(shí)現(xiàn)),編譯器產(chǎn)生的代碼也可能被CPU再次做亂序和并行(out-of-order / parallelism)優(yōu)化。現(xiàn)代CPU,包括各款奔騰在內(nèi),都使用亂序和并行來(lái)盡量避免流水線等待內(nèi)存,IO,以提高執(zhí)行效率。

          另外,一個(gè)像樣的CPU,是不會(huì)把那個(gè) TEMP 寫(xiě)到內(nèi)存里,再回頭花費(fèi)幾百個(gè) CPU CLOCK 從內(nèi)存里拿回來(lái),賦值給 m_instance 寄存器,然后再把 m_instance 寫(xiě)回內(nèi)存的。其實(shí)遠(yuǎn)遠(yuǎn)不到 CPU 的層面,編譯器已經(jīng)會(huì)發(fā)現(xiàn)這樣很不核算從而通通優(yōu)化調(diào)。即使編譯器不優(yōu)化,CPU也不會(huì)這么做,而是把m_instance寫(xiě)到緩存里。既然牽扯到緩存,如果你有兩個(gè)以上的物理CPU,或者兩個(gè)以上的物理內(nèi)核,或者兩個(gè)以上的虛擬內(nèi)核(所謂超線程),就不能保證各個(gè)執(zhí)行單元看到的是一個(gè)值,除非用 synchronized{} 告訴 JVM 需要作 同步/緩存提交。

          總而言之,在 JAVA 5 之前的JAVA 內(nèi)存模型下,如果使用優(yōu)化編譯器,或者使用了并行架構(gòu)的硬件,那么DCL是“不保證正確”的,無(wú)論用什么辦法修改,就好像永動(dòng)機(jī)不可能實(shí)現(xiàn)一樣。

          好消息是:包含在 JAVA 5 的 JSR 133 之下,如果把 m_instance 改為 volatile 變量,那么DCL至少是“功能正確”的。因?yàn)?JSR 133 更改了 volatile 的語(yǔ)義,使 volatile 變量的讀寫(xiě)與 synchronized 有相當(dāng)?shù)暮x。

          壞消息是:如果把 m_instance 改為 volatile 變量,那么DCL雖然功能正確,效率卻和在 synchronized block 內(nèi)部單一檢查完全一樣,甚至更差一點(diǎn)(因?yàn)闄z查了兩次)。所以,DCL 就完全不具有物理意義了。

          再次總而言之,DCL 在 JAVA 是沒(méi)有價(jià)值的。值得一提的是,DCL在 C# 里也同樣沒(méi)有意義。抄的就是抄的,連 BUG 都一起抄去了。呵呵。

          不過(guò)老板關(guān)于SPRING的評(píng)價(jià)在這里并不合乎語(yǔ)境。如果我的需求必須計(jì)較 SYNCHRONIZED 所帶來(lái)的幾百個(gè) CPU CLOCK 的性能損失,又怎么可能容忍 XML PARSING 和 REFLECTION?   回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-02-27 22:37 dreamstone

          今天終于發(fā)現(xiàn)了一種寫(xiě)法可以試用,具體看這里:
          http://www.aygfsteel.com/dreamstone/archive/2007/02/27/101000.html  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-02-28 17:28 dreamstone

          這里有許多關(guān)于單態(tài)的討論,觀點(diǎn)是盡量不使用單態(tài),而使用IOC容器
          http://www.jdon.com/jive/thread.jsp?forum=91&thread=17578  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-05-22 13:48 passager

          @wueddie
          你說(shuō)的對(duì).用靜態(tài)變量是可以實(shí)現(xiàn)的.
          見(jiàn)http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2007-05-23 10:44 dreamstone

          @passager
          我的意思并不是說(shuō)靜態(tài)變量不可以實(shí)現(xiàn)。
          而是靜態(tài)變量的實(shí)現(xiàn)也存在一些問(wèn)題,特別是存在一些陷阱,這樣就造成一個(gè)問(wèn)題,當(dāng)?shù)谝粋€(gè)人寫(xiě)完了是沒(méi)有問(wèn)題的,但隨著需求的復(fù)雜,后邊也許會(huì)換別人來(lái)維護(hù)這個(gè)東西,如果代碼中有陷阱,就說(shuō)明第一個(gè)人留下的并不是一個(gè)好的方法。因?yàn)榘凑粘@韥?lái)說(shuō),維護(hù)代碼的可用時(shí)間比開(kāi)發(fā)時(shí)要趕的多。更容易出錯(cuò)。  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break)[未登錄](méi) 2012-12-08 22:51 eric

          你好,請(qǐng)問(wèn)一下

          synchronized(Singleton.class) {

          if(instance == null) {

          instance = new Singleton();

          }
          }



          這段代碼不是說(shuō)明了若線程A取得鎖,執(zhí)行實(shí)例化的操作的話,線程B必須要等到線程A執(zhí)行完這段代碼后并釋放鎖后才能進(jìn)入同步塊吧?是不是說(shuō)線程A中只取得了把單例對(duì)象的地址賦值給instance而沒(méi)有執(zhí)行對(duì)象的init方法就把鎖釋放了讓線程B可以取得鎖?  回復(fù)  更多評(píng)論   

          # re: Java中的模式 --單態(tài) (部分翻譯 double-checked locking break) 2013-07-04 11:29 wyt

          這種寫(xiě)法是可靠的,但是性能會(huì)比較低。  回復(fù)  更多評(píng)論   

          主站蜘蛛池模板: 梅州市| 宁武县| 临西县| 伊宁县| 五河县| 金堂县| 淳化县| 布尔津县| 方正县| 佳木斯市| 清河县| 叙永县| 潍坊市| 准格尔旗| 阜城县| 潼关县| 梅州市| 临江市| 遵化市| 新丰县| 闸北区| 高雄市| 奉化市| 汶川县| 酒泉市| 汾西县| 常山县| 连城县| 龙口市| 台州市| 疏勒县| 临安市| 咸宁市| 南部县| 八宿县| 塔城市| 大渡口区| 贵溪市| 辽宁省| 梅州市| 乳山市|