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

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

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

    xylz,imxylz

    關注后端架構、中間件、分布式和并發編程

       :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

    在這個小結里面重點討論原子操作的原理和設計思想。

    由于在下一個章節中會談到鎖機制,因此此小節中會適當引入鎖的概念。

    Java Concurrency in Practice中是這樣定義線程安全的:

    當多個線程訪問一個類時,如果不用考慮這些線程在運行時環境下的調度和交替運行,并且不需要額外的同步及在調用方代碼不必做其他的協調,這個類的行為仍然是正確的,那么這個類就是線程安全的。

    顯然只有資源競爭時才會導致線程不安全,因此無狀態對象永遠是線程安全的

    原子操作的描述是: 多個線程執行一個操作時,其中任何一個線程要么完全執行完此操作,要么沒有執行此操作的任何步驟,那么這個操作就是原子的。

    枯燥的定義介紹完了,下面說更枯燥的理論知識。

    指令重排序

    Java語言規范規定了JVM線程內部維持順序化語義,也就是說只要程序的最終結果等同于它在嚴格的順序化環境下的結果,那么指令的執行順序就可能與代碼的順序不一致。這個過程通過叫做指令的重排序。指令重排序存在的意義在于:JVM能夠根據處理器的特性(CPU的多級緩存系統、多核處理器等)適當的重新排序機器指令,使機器指令更符合CPU的執行特點,最大限度的發揮機器的性能。

    程序執行最簡單的模型是按照指令出現的順序執行,這樣就與執行指令的CPU無關,最大限度的保證了指令的可移植性。這個模型的專業術語叫做順序化一致性模型。但是現代計算機體系和處理器架構都不保證這一點(因為人為的指定并不能總是保證符合CPU處理的特性)。

    我們來看最經典的一個案例。

    package xylz.study.concurrency.atomic;

    public class ReorderingDemo {

       
    static int x = 0, y = 0, a = 0, b = 0;

       
    public static void main(String[] args) throws Exception {

           
    for (int i = 0; i < 100; i++) {
                x
    =y=a=b=0;
                Thread one
    = new Thread() {
                   
    public void run() {
                        a
    = 1;
                        x
    = b;
                    }

                }
    ;
                Thread two
    = new Thread() {
                   
    public void run() {
                        b
    = 1;
                        y
    = a;
                    }

                }
    ;
                one.start();
                two.start();
                one.join();
                two.join();
                System.out.println(x
    + " " + y);
            }

        }
     

    }



    在這個例子中one/two兩個線程修改區x,y,a,b四個變量,在執行100次的情況下,可能得到(0 1)或者(1 0)或者(1 1)。事實上按照JVM的規范以及CPU的特性有很可能得到(0 0)。當然上面的代碼大家不一定能得到(0 0),因為run()里面的操作過于簡單,可能比啟動一個線程花費的時間還少,因此上面的例子難以出現(0,0)。但是在現代CPU和JVM上確實是存在的。由于run()里面的動作對于結果是無關的,因此里面的指令可能發生指令重排序,即使是按照程序的順序執行,數據變化刷新到主存也是需要時間的。假定是按照a=1;x=b;b=1;y=a;執行的,x=0是比較正常的,雖然a=1在y=a之前執行的,但是由于線程one執行a=1完成后還沒有來得及將數據1寫回主存(這時候數據是在線程one的堆棧里面的),線程two從主存中拿到的數據a可能仍然是0(顯然是一個過期數據,但是是有可能的),這樣就發生了數據錯誤。

    在兩個線程交替執行的情況下數據的結果就不確定了,在機器壓力大,多核CPU并發執行的情況下,數據的結果就更加不確定了。

    Happens-before法則

    Java存儲模型有一個happens-before原則,就是如果動作B要看到動作A的執行結果(無論A/B是否在同一個線程里面執行),那么A/B就需要滿足happens-before關系。

    在介紹happens-before法則之前介紹一個概念:JMM動作(Java Memeory Model Action),Java存儲模型動作。一個動作(Action)包括:變量的讀寫、監視器加鎖和釋放鎖、線程的start()和join()。后面還會提到鎖的的。

    happens-before完整規則:

    (1)同一個線程中的每個Action都happens-before于出現在其后的任何一個Action。

    (2)對一個監視器的解鎖happens-before于每一個后續對同一個監視器的加鎖。

    (3)對volatile字段的寫入操作happens-before于每一個后續的同一個字段的讀操作。

    (4)Thread.start()的調用會happens-before于啟動線程里面的動作。

    (5)Thread中的所有動作都happens-before于其他線程檢查到此線程結束或者Thread.join()中返回或者Thread.isAlive()==false。

    (6)一個線程A調用另一個另一個線程B的interrupt()都happens-before于線程A發現B被A中斷(B拋出異常或者A檢測到B的isInterrupted()或者interrupted())。

    (7)一個對象構造函數的結束happens-before與該對象的finalizer的開始

    (8)如果A動作happens-before于B動作,而B動作happens-before與C動作,那么A動作happens-before于C動作。

    volatile語義

    到目前為止,我們多次提到volatile,但是卻仍然沒有理解volatile的語義。

    volatile相當于synchronized的弱實現,也就是說volatile實現了類似synchronized的語義,卻又沒有鎖機制。它確保對volatile字段的更新以可預見的方式告知其他的線程。

    volatile包含以下語義:

    (1)Java 存儲模型不會對valatile指令的操作進行重排序:這個保證對volatile變量的操作時按照指令的出現順序執行的。

    (2)volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其他對CPU不可見的地方,每次總是從主存中讀取volatile變量的結果。也就是說對于volatile變量的修改,其它線程總是可見的,并且不是使用自己線程棧內部的變量。也就是在happens-before法則中,對一個valatile變量的寫操作后,其后的任何讀操作理解可見此寫操作的結果。

    盡管volatile變量的特性不錯,但是volatile并不能保證線程安全的,也就是說volatile字段的操作不是原子性的,volatile變量只能保證可見性(一個線程修改后其它線程能夠理解看到此變化后的結果),要想保證原子性,目前為止只能加鎖!

    volatile通常在下面的場景:

     

    volatile boolean done = false;



       
    while( ! done ){
            dosomething();
        }

     

    應用volatile變量的三個原則:

    (1)寫入變量不依賴此變量的值,或者只有一個線程修改此變量

    (2)變量的狀態不需要與其它變量共同參與不變約束

    (3)訪問變量不需要加鎖

     

    這一節理論知識比較多,但是這是很面很多章節的基礎,在后面的章節中會多次提到這些特性。

    本小節中還是沒有談到原子操作的原理和思想,在下一節中將根據上面的一些知識來介紹原子操作。

     

    參考資料:

    (1)Java Concurrency in Practice

    (2)正確使用 Volatile 變量

     



    ©2009-2014 IMXYLZ |求賢若渴
    posted on 2010-07-03 20:40 imxylz 閱讀(46608) 評論(16)  編輯  收藏 所屬分類: J2EEJava Concurrency

    評論

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 2010-07-04 00:41 滴水
    講到這篇開始有點意思了,呵呵,加油。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-07-23 16:37 thebye85
    “volatile并不能保證線程安全的,也就是說volatile字段的操作不是原子性的”
    既然volatile保證變量在主內存,多線程操作的應該在同一內存,能說下為什么多線程下不是原子性的嗎?謝謝  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-07-23 16:56 xylz
    @thebye85
    比如主存中是i=10,兩個線程同時讀取到i=10,都需要++,那么調用完成后可能i=11,而我們的目標是i=12,所以不是一致的。如果一個加一個減結果就更加難以預料了。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-07-23 22:36 thebye85
    @xylz
    謝謝回復。再請教下兩個線程中的i++后的中間臨時結果(在沒寫入到i之前),是保存在哪的,是在主存中而新開辟的內存嗎?  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-07-23 23:07 xylz
    @thebye85

    Java內存分為堆和棧。而棧通常是線程獨有的,堆是線程共享的,通常對于一個變量而言,JVM盡可能的保存在棧中,因為棧通常是寄存器、CPU緩存的高速設備,所以棧一般比較小,而堆比較大一般在內存中。對于一個volatile變量,JMM(Java存儲模型)保證對所有線程是共享,所以一定不會存在寄存器、CPU緩存等棧上,我查了英文本的《The Java Language Specification, Third Editon》,上面沒有說具體存放于何處,我猜就可能存放于主存上,也就是堆上。

    所以通常而言,操作一個volatile變量要比非volatile變量開銷要大,但是這個差別很小,相對于鎖機制來說可以忽略不計。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-07-31 12:53 Johnny Jian
    @thebye85
    應該是操作棧吧  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-08-12 15:20 thebye85
    @Johnny Jian
    我是說假如變量i聲明為volatile,多線程操作i都是在共享內存(堆)上吧?
    保存各自線程棧的話,怎么保證i的可見性?  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2010-08-12 15:27 Johnny Jian
    @thebye85
    i++是非原子操作,你只能通過其他手段來保證咯  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2011-07-09 23:24 ieye
    @xylz
    我覺得操作的中間結果應該還是在線程棧中的,這個計算結果又沒有必要對其他線程可見,只有寫回主存的時候才對其他線程可見。個人理解  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2011-08-02 08:36 喜樂
    @ieye
    并且寫回主存的時候不是原子的, 這就是問題所在。 比如一個LONG, 在32位機上可以寫高位也可以先寫低位。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2011-08-09 14:07 那時花開
    在多核的環境中,高速緩存有多個,如果這個變量不在主存存放的話,那么線程讀到的值就可能是錯的,未及時同步的。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2011-11-24 17:03 duanjb
    我認為局部變量才是保持在棧里面的,對于全局變量則應該是放在堆里面了!要不然沒法保證可見性。
    volatile boolean done = false;



    while( ! done ){
    dosomething();
    }

    對于這個問題,<<effective java>中說明如果不加volatile,程序會被jvm優化為
    if(!done)
    {
    while(true)
    {
    dosomething();
    }
    }
    這意味一旦進入到while中就會死循環。。。。。。
    但是在多核cpu下,我的測試是就算進入了while循環也會停止掉!
    http://www.iteye.com/topic/1118193
    這個帖子有不少人討論  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2012-01-19 10:35 jasonlmq
    申明為volatile的屬性變量,是存儲在堆的主存中。無論任何線程訪問此變量都是直接從主內存中讀取,不是從線程自己的堆中取,更不是從線程的棧中讀取。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2012-10-25 12:34 vitamin.x
    @thebye85
    雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀取-修改-寫入操作序列組成的組合操作,必須以原子方式執行,而 volatile 不能提供必須的原子特性。實現正確的操作需要使 x 的值在操作期間保持不變,而 volatile 變量無法實現這點。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則 2013-01-08 18:26 mgampkay
    對volatile變量的讀會從內存讀入寄存器,寫會把寄存器內容寫回內存。
    可見性就是指我修改后你再讀,你讀到的就是我修改后的內容。

    例如有一個volatile變量a,初值為0,依次執行a = 1; a = 2;這樣就有兩次對內存的寫入。在a = 1后,如果有線程讀a,就會從內存里都到1。

    對非volatile變量b, 依次執行b = 1; b = 2; 第一次修改可能只是對寄存器修改,而沒有寫回內存。這樣執行b = 1后,另一個線程讀b就會都到內存中的0。

    所以說volatile變量可以保證可見性。  回復  更多評論
      

    # re: 深入淺出 Java Concurrency (4): 原子操作 part 3 指令重排序與happens-before法則[未登錄] 2013-03-03 14:34 teasp
    博主關于hanppens-before規則的第5條有錯誤。  回復  更多評論
      


    ©2009-2014 IMXYLZ
    主站蜘蛛池模板: 亚洲电影免费观看| 8x成人永久免费视频| 日韩在线a视频免费播放| 亚洲综合精品成人| 在线观看免费a∨网站| 亚洲色偷偷综合亚洲AV伊人蜜桃| 97人伦色伦成人免费视频| 亚洲五月综合网色九月色| aa级一级天堂片免费观看| 亚洲一区二区三区丝袜| 国产美女精品视频免费观看| 亚洲av无码一区二区三区四区 | 怡红院亚洲红怡院在线观看| 妞干网免费观看视频| 日本亚洲中午字幕乱码| 国产a v无码专区亚洲av| 国产免费拔擦拔擦8X高清在线人 | 国产成人久久AV免费| 亚洲精品电影在线| 成人av免费电影| 一级美国片免费看| 亚洲小视频在线观看| 蜜桃视频在线观看免费网址入口| 亚洲成aⅴ人片久青草影院按摩| vvvv99日韩精品亚洲| 日本免费电影一区二区| 亚洲国产精品成人综合色在线婷婷| 在线观看免费宅男视频| 中国在线观看免费的www| 久久亚洲AV成人无码国产| 在线免费观看a级片| 99久久免费国产精精品| 亚洲男人电影天堂| 亚洲AV无码一区二三区| 香港a毛片免费观看| 亚洲A∨精品一区二区三区下载| 国产gv天堂亚洲国产gv刚刚碰| 67pao强力打造高清免费| 污视频网站在线免费看| 亚洲精品永久www忘忧草| 亚洲伊人久久综合影院|