java內存模型——重排序

線程安全問題歸納來講表現爲三個方面:原子性,可見性和有序性。

在多核處理器的環境下:編譯器可能改變兩個操做的前後順序;處理器可能不是徹底依照程序的目標代碼所指定的順序執行命令;一個處理器執行的多個操做,在其餘處理器的角度來看,其順序可能與目標代碼所指定的順序不一致。這種現象就叫重排序。java

在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序。重排序分3種類型。

  1. 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。
  2. 指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。
  3. 內存系統的重排序。因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。

內存重排序類型:

重排序類型 含義
LoadLoad重排序 該重排序指一個處理器上前後執行兩個讀內存操做L1和L2,其餘處理器對這兩個內存操做的感知順序多是L2——>L1,即L1被重排序到L2以後。
StoreStore重排序 該重排序指一個處理器上前後執行兩個寫內存操做W1和W2,其餘處理器對這兩個內存操做的感知順序多是W2——>W1,即W1被重排序到W2以後。
LoadStore重排序 該重排序指一個處理器上前後執行讀內存操做L1和寫內存操做W2,其餘處理器對這兩個內存操做的感知順序多是W2——>L1,即L1被重排序到W2以後。
StoreLoad重排序 該重排序指一個處理器上前後執行寫內存操做W1和讀內存操做L2,其餘處理器對這兩個內存操做的感知順序多是L2——>W1,即W1被重排序到L2以後。

內存重排序與具體的處理器微架構有關,基於不一樣微架構的處理器所容許的內存重排序是不一樣的,這裏再也不闡述。編程


重排序可能會致使多線程程序出現內存可見性問題

  • 對於編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序緩存

  • 對於處理器重排序,JMM的處理器重排序規則會要求Java編譯器在生成指令序列時,插入特定類型的內存屏障指令,經過內存屏障指令來禁止特定類型的處理器重排序。安全

常見的處理器都不容許對存在數據依賴的操做作重排序

數據依賴性: 若是兩個操做訪問同一個變量,且這兩個操做中有一個爲寫操做,此時這兩個操做之間就存在數據依賴性。數據依賴分爲下列3種類型:
1.寫後讀:a=1;b=a;
2.寫後寫:a=1;a=2;
3.讀後寫:a=b;b=1;

爲了遵照as-if-serial語義,編譯器和處理器在重排序時,會遵照數據依賴性,編譯器和處理器不會改變存在數據依賴關係的兩個操做的執行順序。由於這種重排序會改變執行結果。
不一樣處理器之間和不一樣線程之間的數據依賴性不被編譯器和處理器考慮。多線程

在單線程程序中,對存在控制依賴的操做重排序,不會改變執行結果(這也是as-if-serial語義容許對存在控制依賴的操做作重排序的緣由);但在多線程程序中,對存在控制依賴的操做重排序,可能會改變程序的執行結果。架構


重排序對多線程的影響

image
當操做1和操做2重排序時
image
當操做3和操做4重排序時
image
重排序在這裏破壞了多線程程序的語義!併發

經過加鎖同步可解決該問題
image性能


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

  • 不管是編譯器仍是處理器,都須要遵循如下重排序規則:
  1. 臨界區內的操做不容許被重排序到臨界區以外
  2. 臨界區內的操做容許被重排序
  3. 臨界區外的操做之間能夠被重排序
  4. 鎖申請與鎖釋放操做不能被重排序
  5. 兩個鎖申請操做不能被重排序
  6. 兩個鎖釋放操做不能被重排序
  7. 臨界區外的操做能夠被重排序到臨界區以內

參考資料:
1.Java併發編程的藝術(方騰飛 魏鵬 程曉明 著)
2.Java多線程編程實戰指南(黃文海 著)優化

相關文章
相關標籤/搜索