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

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

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

    走自己的路

    路漫漫其修遠兮,吾將上下而求索

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      50 隨筆 :: 4 文章 :: 118 評論 :: 0 Trackbacks
     

    在這本書中文版的第219頁有個例子,講lazy load時用到double check,double check比直接用同步的好處是,當Singleton初始化后,就不會有額外的同步操作。它的例子是

     1public class Singleton {
     2    private volatile static Singleton INSTANCE;
     3    
     4    private Singleton() {
     5        
     6    }

     7    
     8    public static Singleton getInstance() {
     9        if(INSTANCE == null{
    10            synchronized (Singleton.class{
    11                if(INSTANCE == null{
    12                    INSTANCE = new Singleton();
    13                }

    14            }

    15        }

    16        return INSTANCE;
    17    }

    18}

    19
     

            不幸的是,雙重檢查不會保證正常工作,因為編譯器會在Singleton的構(gòu)造方法被調(diào)用之前隨意給INSTANCE先付一個值。如果在INSTANCE引用被賦值之后而被初始化之前線程1被切換,線程2就會被返回一個對未初始化完全的單例類實例的引用。這樣在程序的其他方法中使用時可能會出現(xiàn)未知的錯誤。

     

    個人一開始認為正確的寫法,應該是這樣的

     1public class SingletonNew {
     2    private volatile static SingletonNew INSTANCE;
     3    
     4    private SingletonNew() {
     5        
     6    }

     7    
     8    public static SingletonNew getInstance() {
     9        SingletonNew tempInstance = INSTANCE;
    10        if(tempInstance == null{
    11            synchronized (Singleton.class{
    12                tempInstance = INSTANCE;    //(1)
    13                if(tempInstance == null{
    14                    INSTANCE = tempInstance = new SingletonNew(); //(2)
    15                }

    16            }

    17        }

    18        return tempInstance;
    19    }

    20}

    21
     

          

         利用一個tempInstance局部變量來排除返回實例未初始化完全的情況。因為每次判斷的都是局部變量,每個線程都會有一個自己的tempInstance,這樣就保證每個線程的tempInstance要么是初始化完全的要么就是未初始化的,不會出現(xiàn)中間的情況。要注意的是SingletonNew(1)處是不能去掉的,比如線程構(gòu)造了一個實例,線程2此時等待在那里,線程2得到鎖,判斷tempInstance == null結(jié)果是true,又初始化了一次,這就不是單例了。(2)處的賦值順序也是不能顛倒的,如果顛倒就會出現(xiàn)和Singleton類一樣的情形。


    請大家詳細討論,詳細解釋一下。

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        其實這兩種寫法在舊的JMM上都是錯誤的,在新的JMM上都是對的,錯誤的原因主要是JMM對代碼的重新排序和優(yōu)化,新的JMM又對volatile的語義進行了擴展,保證了double-check的正確性。很抱歉一開始讓一些博友產(chǎn)生了困惑,謝謝大家的熱心的討論和回帖,我的主要問題就是出現(xiàn)在對JMM了解不夠深入,只是碎片式的了解一些,沒有很好的了解編譯器對代碼的重新排序和優(yōu)化,當然編譯原理課上是學過的。二又沒有很好的掌握到volatile的新的語義。其實對一些細節(jié)了解清楚,可以避免我們的代碼出現(xiàn)一些奇怪的問題,特別是在多線程環(huán)境中。

     

        Jvm編譯器會對生成的代碼進行優(yōu)化,重新排序,甚至移除它認為不必要的代碼,volatile變量之間也是沒有順序保證的。然而jvm保證了classloader load字節(jié)碼和靜態(tài)變量初始化的同步性,所有把singleton設置為靜態(tài)變量是沒有問題的。JMM保證了單線程執(zhí)行的效果和程序的順序是相同的。JVM對代碼的重新排序和優(yōu)化是對于程序不可見的,所以在例子2中我不應該假設執(zhí)行的順序。在讀volatile變量之前,寫行為確保執(zhí)行完畢,并且更新的值會從線程工作內(nèi)存(CPU緩存,寄存器)刷新到主內(nèi)存中,JMM禁止volatile讀入寄存器,其他線程讀取時也會重新load到工作內(nèi)存中,保證了一致性和可見性,避免讀取臟數(shù)據(jù)。以前一直以為volatile涉及的只是變量可見性問題,或者說對可見性的適用范圍沒有很好的理解,并不涉及JMM順序性和原子性問題。新的JMM對它進行了擴展,它對volatile變量的重新排序也做了限制。在舊的內(nèi)存模型當中,volatile變量的多次訪問之間是不能重新排序的,但是它們能在和對非volatile變量訪問代碼之間進行重新排序,新的內(nèi)存模型不同的是,volatile訪問行為在和非volatile變量的訪問行為的代碼之間重新排序加了一些限制。對volatile的寫行為就和synchronize方法或block釋放監(jiān)視器(鎖)的效果是一樣的,對volatile字段的讀操作和監(jiān)視器(鎖)的申請效果也是一樣的。新的模型在volatile字段訪問上做了一些嚴格的限制,只對當前線程可見的變量寫入到volatile共享變量f后,當其他線程讀取f后就是可見的。

    下面這個簡單的例子:

    class VolatileExample {
     int x = 0;
     volatile boolean v = false;
     public void writer() {
        x = 42;
        v = true;
     }
     
     public void reader() {
        if (v == true) {
          //uses x - guaranteed to see 42.
        }
     }
    }

    假設當前一個線程正在調(diào)用writer方法,其他線程正在調(diào)用reader方法,writer方法中對v的寫行為將對x的寫行為釋放到了內(nèi)存中,v變量的讀取,又重新從內(nèi)存中獲取了新值。因此,如果讀方法看到了v的值被設為true,也保證了它在這之前就可以看到x的新值42,但這在舊的內(nèi)存模型中是不保證的。如果v不是volatile的,編譯器可能就會對writerreader中的代碼進行重新排序,reader方法的訪問有可能得到的x就是0. 可見在新的JMM中,volatile的語義得到了很好的加強,每次對volatile字段的讀和寫可看作是都是半同步。這種順序性(happen-before關(guān)系)是針對同一個volatile字段而言的,對不同volatile字段的讀取還是沒有這種順序保證的。在新的JMM下,用volatile就可以解決問題,線程1實例的初始化和線程2的讀取volatile變量就存在一個happen-before關(guān)系。

    JMM對順序性只是提出了一些規(guī)則,具體如何重新排序還是不得而知。

    參考文章:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering
              《JAVA Language Specification》 17.4



    posted on 2008-07-23 19:51 叱咤紅人 閱讀(2709) 評論(22)  編輯  收藏 所屬分類: Design and Analysis Pattern 、J2SE and JVM

    評論

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-23 21:20 路過
    即使第一種寫法有問題,你怎么能證明第二種寫法就是對的呢?
    照你的說法INSTANCE是一個沒有完全初始化的對象,那么tempInstance是復制的引用而已,前者沒有完全初始化后者也肯定是一樣的。我完全沒看出來多賦值一次有什么好處。
    請設計一個試驗,謝謝!
      回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-23 23:10 Jarod
    博主是在亂說

    private volatile static SingletonNew INSTANCE;
    static {
    System.out.println(INSTANCE); //null
    }

    就算“因為編譯器會在Singleton的構(gòu)造方法被調(diào)用之前隨意給INSTANCE先付一個值”成立了,代碼2不見得就解決了問題  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤[未登錄] 2008-07-24 06:29 叱咤紅人
    @Jarod
    謝謝回復.
    INSTANCE = new Singleton();我的理解是調(diào)用了構(gòu)造函數(shù),在構(gòu)造之前會先生成一個臨時的值,引用指向一個臨時的地方,具體以前在那里看到的也不太記得了.所以第一種方法線程1進入構(gòu)造函數(shù)后,線程2會得到一個不是null的臨時值,所以會得到一個未初始化完全的對象.第二種方法,對全局靜態(tài)變量INSTANCE,沒有用它來作為double check的條件,而是使用了tempInstance局部變量,每個線程都會生成一個自己的tempInstance   回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 07:48 朱遠翔-Apusic技術(shù)顧問
    @叱咤紅人
    在進入初始化之前使用的是線程同步,那么就不存在線程切換的問題呀?   回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 08:13 ldd600
    @朱遠翔-Apusic技術(shù)顧問
    謝謝回復。
    因為采用了double check,延遲了同步。所以還是存在線程切換的問題。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 08:46 5452
    double check這個東西,現(xiàn)在說不清楚,這種方法沒有辦法確定就是單例。
    JVM建立對象的過程是這樣的:1、先分配一塊內(nèi)存,2、然后把內(nèi)存地址賦值給對象的引用,3、然后調(diào)用類的構(gòu)造函數(shù),生成對象。
    如果一個線程執(zhí)行到第二步的時候,另外一個線程進入這個方法,這個時候INSTANCE已經(jīng)不是空的了,但是實際上還沒有初始化,這樣的話,一定會出問題的~
      回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 09:24 路過
    樓主所提出的問題我可以理解,可是無法理解
    “ if(INSTANCE == null) {”

    “ SingletonNew tempInstance = INSTANCE;
    if(tempInstance == null) {”
    這兩句會得到不同的判斷。
    如果INSTANCE沒有完全初始話,tempInstance也肯定是一樣啊。雖然“每個線程都會生成一個自己的tempInstance”,其實這些tempInstance和INSTANCE沒有區(qū)別,它們是不同的引用,但是指向同一個對象。
      回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 09:29 ldd600
    @路過
    謝謝回復
    每個線程生成自己的tempInstance是指這句
    INSTANCE = tempInstance = new SingletonNew(); //(2)
    這句保證了INSTANCE的構(gòu)造的完全性。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 09:38 yswift
    JAVA不支持double check,不管怎么修改,只要用到double check都是錯的,在C++中,書中的例子是完全可以正常工作的。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 10:07 路人
    好像都沒說道正點上,注意volatile關(guān)鍵字。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 10:29 白色天堂
    這段代碼在jdk1.5之后完全沒有問題。之前的版本可能出問題。

    你也沒有理解出錯的原因,所作的改動完全是畫蛇添足。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 11:06 dennis
    無語了,沒看到volatile關(guān)鍵字嗎?  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中的一個錯誤 2008-07-24 11:07 dennis
    @白色天堂
    也不能說完全沒問題,有的jvm實現(xiàn)在volatile的語義上還是有問題的,只能說在sun jdk1.5及以后版本是沒有問題的。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-24 12:38 路過
    麻煩樓上的講一下為什么
    INSTANCE = tempInstance = new SingletonNew(); //(2)
    這句保證了INSTANCE的構(gòu)造的完全性。
    謝謝。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-24 12:42 路過
    volatile在這段程序里起了什么作用呢?
    樓主說的是得到了一個引用但是引用指向的對象是沒有完全初始化的,又不是說對象已經(jīng)初始化了還是有程序得到了null的引用。
    麻煩樓上的解釋一下,謝謝。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-24 13:09 zhuxing
    @yswift

    yswift同志說的一針見血!  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-25 09:32 dennis
    http://www.ibm.com/developerworks/java/library/j-dcl.html?loc=j

    看看這篇文章,俺就不多說了。原因就在于JMM模型的out-of-order writes問題。jdk5通過正確的實現(xiàn)volatile語義能保證對聲明為volatile的變量的讀和寫不會被后續(xù)的讀和寫所重排序,因而解決了這個問題。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-25 11:09 路過
    The best solution to this problem is to accept synchronization or use a static field.
    多謝dennis,學習了。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-26 21:44 叱咤紅人
    謝謝大家尤其是dennis的熱情討論和回復,其實這兩種寫法在舊的JMM上都是錯誤的,在新的JMM上都是對的,我主要還是沒有對JMM有更深入的理解,抱歉,繼續(xù)努力好好工作,好好學習,大家分享。  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-26 21:46 叱咤紅人
    其實這兩種寫法在舊的JMM上都是錯誤的,在新的JMM上都是對的,錯誤的原因主要是JMM對代碼的重新排序和優(yōu)化,新的JMM又對volatile的語義進行了擴展,保證了double-check的正確性。很抱歉一開始讓一些博友產(chǎn)生了困惑,謝謝大家的熱心的討論和回帖,我的主要問題就是出現(xiàn)在對JMM了解不夠深入,只是碎片式的了解一些,沒有很好的了解編譯器對代碼的重新排序和優(yōu)化,當然編譯原理課上是學過的。二又沒有很好的掌握到volatile的新的語義。其實對一些細節(jié)了解清楚,可以避免我們的代碼出現(xiàn)一些奇怪的問題,特別是在多線程環(huán)境中。


    Jvm編譯器會對生成的代碼進行優(yōu)化,重新排序,甚至移除它認為不必要的代碼,volatile變量之間也是沒有順序保證的。然而jvm保證了classloader load字節(jié)碼和靜態(tài)變量初始化的同步性,所有把singleton設置為靜態(tài)變量是沒有問題的。JMM保證了單線程執(zhí)行的效果和程序的順序是相同的。JVM對代碼的重新排序和優(yōu)化是對于程序不可見的,所以在例子2中我不應該假設執(zhí)行的順序。在讀volatile變量之前,寫行為確保執(zhí)行完畢,并且更新的值會從線程工作內(nèi)存(CPU緩存,寄存器)刷新到主內(nèi)存中,JMM禁止volatile讀入寄存器,其他線程讀取時也會重新load到工作內(nèi)存中,保證了一致性和可見性,避免讀取臟數(shù)據(jù)。以前一直以為volatile涉及的只是變量可見性問題,或者說對可見性的適用范圍沒有很好的理解,并不涉及JMM順序性和原子性問題。新的JMM對它進行了擴展,它對volatile變量的重新排序也做了限制。在舊的內(nèi)存模型當中,volatile變量的多次訪問之間是不能重新排序的,但是它們能在和對非volatile變量訪問代碼之間進行重新排序,新的內(nèi)存模型不同的是,volatile訪問行為在和非volatile變量的訪問行為的代碼之間重新排序加了一些限制。對volatile的寫行為就和synchronize方法或block釋放監(jiān)視器(鎖)的效果是一樣的,對volatile字段的讀操作和監(jiān)視器(鎖)的申請效果也是一樣的。新的模型在volatile字段訪問上做了一些嚴格的限制,只對當前線程可見的變量寫入到volatile共享變量f后,當其他線程讀取f后就是可見的。

    下面這個簡單的例子:

    class VolatileExample {
    int x = 0;
    volatile boolean v = false;
    public void writer() {
    x = 42;
    v = true;
    }

    public void reader() {
    if (v == true) {
    //uses x - guaranteed to see 42.
    }
    }
    }
    假設當前一個線程正在調(diào)用writer方法,其他線程正在調(diào)用reader方法,writer方法中對v的寫行為將對x的寫行為釋放到了內(nèi)存中,v變量的讀取,又重新從內(nèi)存中獲取了新值。因此,如果讀方法看到了v的值被設為true,也保證了它在這之前就可以看到x的新值42,但這在舊的內(nèi)存模型中是不保證的。如果v不是volatile的,編譯器可能就會對writer和reader中的代碼進行重新排序,reader方法的訪問有可能得到的x就是0. 可見在新的JMM中,volatile的語義得到了很好的加強,每次對volatile字段的讀和寫可看作是都是半同步。這種順序性(happen-before關(guān)系)是針對同一個volatile字段而言的,對不同volatile字段的讀取還是沒有這種順序保證的。在新的JMM下,用volatile就可以解決問題,線程1實例的初始化和線程2的讀取volatile變量就存在一個happen-before關(guān)系。
      回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎? 2008-07-26 21:53 zxbyh
    不用去研究這個!
    使用餓漢單例模式就可以了.

    <<java 與模式>>  回復  更多評論
      

    # re: 《Head First Design Pattern 單例模式》中double check有問題嗎?[未登錄] 2008-08-08 13:26 Chris
    不管哪種方法,在多機的情況下依然還是解決不了單例的問題,現(xiàn)在機器那么廉價,那點延遲初始化所帶來的效率是微乎其微的,完全不需要。  回復  更多評論
      

    主站蜘蛛池模板: 3d动漫精品啪啪一区二区免费| 久久亚洲精品中文字幕无码| 最近免费中文字幕大全免费 | 天堂亚洲国产中文在线| 中文字幕亚洲无线码a| 午夜精品在线免费观看| 在线免费观看你懂的| 国产精品高清免费网站| 亚洲av成人无码网站…| 亚洲日本久久久午夜精品| 99久久亚洲综合精品成人网| 国产亚洲精品国产| 精品国产亚洲一区二区在线观看| 四色在线精品免费观看| 一个人免费观看在线视频www| 91精品啪在线观看国产线免费| 大地资源在线资源免费观看 | 永久黄网站色视频免费直播| 1000部夫妻午夜免费| 日本免费污片中国特一级| 久久久精品国产亚洲成人满18免费网站| 亚洲人成人无码.www石榴 | 女人18毛片免费观看| 国产精品成人免费福利| 日韩在线不卡免费视频一区| 国产免费网站看v片在线| 国产精品无码免费专区午夜| 四虎精品成人免费视频| 免费精品久久久久久中文字幕| 亚洲av无码一区二区三区四区 | 亚洲一级毛片免费看| 99久久久国产精品免费牛牛四川| 免费播放在线日本感人片| 你好老叔电影观看免费| 成人免费乱码大片A毛片| 久久WWW免费人成人片| 免费视频爱爱太爽了| **实干一级毛片aa免费| 国产va精品免费观看| 无码国产精品久久一区免费| 最新猫咪www免费人成|