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多線程編程實戰指南(黃文海 著)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM