天天看點

JAVA記憶體模型--指令重排序

指令重排序

在執行程式時,為了提高性能,編譯器和處理器會對指令做重排序。但是,JMM確定在不同的編譯器和不同的處理器平台之上,通過插入特定類型的Memory Barrier來禁止特定類型的編譯器重排序和處理器重排序,為上層提供一緻的記憶體可見性保證。

  • 編譯器優化重排序:編譯器在不改變單線程程式語義的前提下,可以重新安排語句的執行順序。
  • 指令級并行的重排序:現代處理器采用了指令級并行技術(Instruction-Level Parallelism, ILP)來将多條指令重疊執行。如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。
  • 記憶體系統的重排序:處理器使用緩存和讀寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。

在虛拟機層面,為了盡可能減少記憶體操作速度遠慢于CPU運作速度所帶來的CPU空置的影響,虛拟機會按照自己的一些規則将程式編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以盡可能充分地利用CPU

從java源代碼到最終實際執行的指令序列,會分别經曆下面三種重排序:

JAVA記憶體模型--指令重排序

JVM Happen-Before規則

  • 程式次序規則(Program Order Rule)

在一個線程内,按照代碼順序,書寫在前面的操作先行發生于書寫在後面的操作。準确地說應該是控制流順序而不是代碼順序,因為要考慮分支、循環等結構。

  • 螢幕鎖定規則(Monitor Lock Rule)

一個unlock操作先行發生于後面對同一個對象鎖的lock操作。這裡強調的是同一個鎖,而“後面”指的是時間上的先後順序,如發生在其他線程中的lock操作。

  • volatile變量規則(Volatile Variable Rule)

volatile變量的寫先發生于讀,這保證了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修飾符。

參考資料

【happens-before俗解】http://ifeve.com/easy-happens-before/

【死磕Java并發–Java記憶體模型之happens-before】 http://cmsblogs.com/?p=2102

【JMM Cookbook(一)指令重排】http://ifeve.com/jmm-cookbook-reorderings/