java指令重排序的問題,java指令排序

來源:互聯網
上載者:User

java指令重排序的問題,java指令排序

轉載自於:http://my.oschina.net/004/blog/222069?fromerr=ER2mp62C

指令重排序是個比較複雜、覺得有些不可思議的問題,同樣是先以例子開頭(建議大家跑下例子,這是實實在在可以重現的,重排序的機率還是挺高的),有個感性的認識

/** * 一個簡單的展示Happen-Before的例子. * 這裡有兩個共用變數:a和flag,初始值分別為0和false.在ThreadA中先給     a=1,然後flag=true. * 如果按照有序的話,那麼在ThreadB中如果if(flag)成功的話,則應該a=1,而a=a*1之後a仍然為1,下方的if(a==0)應該永遠不會為 * 真,永遠不會列印. * 但實際情況是:在實驗100次的情況下會出現0次或幾次的列印結果,而實驗1000次結果更明顯,有十幾次列印. */public class SimpleHappenBefore {    /** 這是一個驗證結果的變數 */    private static int a=0;    /** 這是一個標誌位 */    private static boolean flag=false;    public static void main(String[] args) throws InterruptedException {        //由於多線程情況下未必會試出重排序的結論,所以多試一些次        for(int i=0;i<1000;i++){            ThreadA threadA=new ThreadA();            ThreadB threadB=new ThreadB();            threadA.start();            threadB.start();            //這裡等待線程結束後,重設共用變數,以使驗證結果的工作變得簡單些.            threadA.join();            threadB.join();            a=0;            flag=false;        }    }    static class ThreadA extends Thread{        public void run(){        a=1;        flag=true;        }    }    static class ThreadB extends Thread{        public void run(){            if(flag){            a=a*1;            }            if(a==0){            System.out.println("ha,a==0");            }        }    }}

 

例子比較簡單,也添加了注釋,不再詳細敘述。
什麼是指令重排序?有兩個層面:
在虛擬機器層面,為了儘可能減少記憶體操作速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機器會按照自己的一些規則(這規則後面再敘述)將程式編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以儘可能充分地利用CPU。拿上面的例子來說:假如不是a=1的操作,而是a=new byte[1024*1024](分配1M空間)`,那麼它會運行地很慢,此時CPU是等待其執行結束呢,還是先執行下面那句flag=true呢?顯然,先執行flag=true可以提前使用CPU,加快整體效率,當然這樣的前提是不會產生錯誤(什麼樣的錯誤後面再說)。雖然這裡有兩種情況:後面的代碼先於前面的代碼開始執行;前面的代碼先開始執行,但當效率較慢的時候,後面的代碼開始執行並先於前面的代碼執行結束。不管誰先開始,總之後面的代碼在一些情況下存在先結束的可能。
在硬體層面,CPU會將接收到的一批指令按照其規則重排序,同樣是基於CPU速度比緩衝速度快的原因,和上一點的目的類似,只是硬體處理的話,每次只能在接收到的有限指令範圍內重排序,而虛擬機器可以在更大層面、更多指令範圍內重排序。硬體的重排序機制參見《從JVM並發看CPU記憶體指令重排序(Memory Reordering)》
重排序很不好理解,上面只是簡單地提了下其情境,要想較好地理解這個概念,需要構造一些例子和圖表,在這裡介紹兩篇介紹比較詳細、生動的文章《happens-before俗解》和《深入理解Java記憶體模型(二)——重排序》。其中的“as-if-serial”是應該掌握的,即:不管怎麼重排序,單線程程式的執行結果不能被改變。編譯器、運行時和處理器都必須遵守“as-if-serial”語義。拿個簡單例子來說,

public void execute(){    int a=0;    int b=1;    int c=a+b;}

 

 

這裡a=0,b=1兩句可以隨便排序,不影響程式邏輯結果,但c=a+b這句必須在前兩句的後面執行。
從前面那個例子可以看到,重排序在多線程環境下出現的機率還是挺高的,在關鍵字上有volatile和synchronized可以禁用重排序,除此之外還有一些規則,也正是這些規則,使得我們在平時的編程工作中沒有感受到重排序的壞處。
程式次序規則(Program Order Rule):在一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作。準確地說應該是控制流程順序而不是代碼順序,因為要考慮分支、迴圈等結構。
監視器鎖定規則(Monitor Lock Rule):一個unlock操作先行發生於後面對同一個對象鎖的lock操作。這裡強調的是同一個鎖,而“後面”指的是時間上的先後順序,如發生在其他線程中的lock操作。
volatile變數規則(Volatile Variable Rule):對一個volatile變數的寫操作發生於後面對這個變數的讀操作,這裡的“後面”也指的是時間上的先後順序。
線程啟動規則(Thread Start Rule):Thread獨享的start()方法先行於此線程的每一個動作。
線程終止規則(Thread Termination Rule):線程中的每個操作都先行發生於對此線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的傳回值檢測到線程已經終止執行。
線程中斷規則(Thread Interruption Rule):對線程interrupte()方法的調用優先於被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測線程是否已中止。
對象終結原則(Finalizer Rule):一個對象的初始化完成(建構函式執行結束)先行發生於它的finalize()方法的開始。
傳遞性(Transitivity):如果操作A先行發生於操作B,操作B先行發生於操作C,那就可以得出操作A先行發生於操作C的結論。
正是以上這些規則保障了happen-before的順序,如果不符合以上規則,那麼在多線程環境下就不能保證執行順序等同於代碼順序,也就是“如果在本線程中觀察,所有的操作都是有序的;如果在一個線程中觀察另外一個線程,則不符合以上規則的都是無序的”,因此,如果我們的多線程程式依賴於代碼書寫順序,那麼就要考慮是否符合以上規則,如果不符合就要通過一些機制使其符合,最常用的就是synchronized、Lock以及volatile修飾符。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.