1. 程式人生 > >Java內存模型(二)——重排序

Java內存模型(二)——重排序

序列 依賴性 種類 如果 禁止 加載 runtime 屬於 style

一、重排序

  重排序是指為了提高程序的執行效率,編譯器和處理器常常會對語句的執行順序或者指令的執行順序進行重排。

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

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

Java源代碼--->1

編譯器重排-->2指令集重排-->3內存系統級重排序--->最終的程序執行代碼

其中1屬於編譯器重排,2,3是處理器級別的重排。多線程中各種操作延遲或者看似亂序執行的原因都可以歸為重排序。

對於編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對於處理器重排序,JMM的處理器重排序規則會要求java編譯器在生成指令序列時,插入特定類型的內存屏障(memory barriersintel稱之為memory fence)指令,通過內存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。

JMM屬於語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。

二、處理器重排序

  現代的處理器使用寫緩沖區來臨時保存向內存寫入的數據。寫緩沖區可以保證指令流水線持續運行,它可以避免由於處理器停頓下來等待向內存寫入數據而產生的延遲。同時,通過以批處理的方式刷新寫緩沖區,以及合並寫緩沖區中對同一內存地址的多次寫,可以減少對內存總線的占用。雖然寫緩沖區有這麽多好處,但每個處理器上的寫緩沖區,僅僅對它所在的處理器可見。這個特性會對內存操作的執行順序產生重要的影響:處理器對內存的讀/寫操作的執行順序,不一定與內存實際發生的讀/寫操作順序一致!

三、、內存屏障指令

  為了保證內存可見性,java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。

JMM把內存屏障指令分為下列四類:

  StoreLoad Barriers是一個“全能型”的屏障,它同時具有其他三個屏障的效果。現代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執行該屏障開銷會很昂貴,因為當前處理器通常要把寫緩沖區中的數據全部刷新到內存中(buffer fully flush)。

三、數據依賴

  如果兩個操作訪問同一個變量,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在數據依賴性。數據依賴分下列三種類型:

  前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序。  

  上面三種情況,只要重排序兩個操作的執行順序,程序的執行結果將會被改變。

  註意,這裏所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的數據依賴性不被編譯器和處理器考慮。

四、as-if-serial語義

  as-if-serial語義的意思指:不管怎麽重排序(編譯器和處理器為了提高並行度),(單線程)程序的執行結果不能被改變。編譯器,runtime 和處理器都必須遵守as-if-serial語義。為了遵守as-if-serial語義,編譯器和處理器不會對存在數據依賴關系的操作做重排序,因為這種重排序會改變執行結果。但是,如果操作之間不存在數據依賴關系,這些操作可能被編譯器和處理器重排序

Java內存模型(二)——重排序