1,指令重排序
大多數現代微處理器都會采用將指令亂序執行(out-of-order execution,簡稱OoOE或OOE)的方法,
在條件允許的情況下,直接運行當前有能力立即執行的后續指令,避開獲取下一條指令所需數據時造成的等待。
通過亂序執行的技術,處理器可以大大提高執行效率。 除了處理器,常見的Java運行時環境的JIT編譯器也會做指令重排序操作,即生成的機器指令與字節碼指令順序不一致。
2,as-if-serial語義
As-if-serial語義的意思是,所有的動作(Action)都可以為了優化而被重排序,但是必須保證它們重排序后的結果和程序代碼本身的應有結果是一致的。
Java編譯器、運行時和處理器都會保證單線程下的as-if-serial語義。
ps:即指令好像是連續的,是對這種執行效果特性的一個說法。
為了保證這一語義,重排序不會發生在有數據依賴的操作之中。
3,內存訪問重排序與內存可見性
計算機系統中,為了盡可能地避免處理器訪問主內存的時間開銷,處理器大多會利用緩存(cache)以提高性能。
即緩存中的數據與主內存的數據並不是實時同步的,各CPU(或CPU核心)間緩存的數據也不是實時同步的。
這導致在同一個時間點,各CPU所看到同一內存地址的數據的值可能是不一致的。
從程序的視角來看,就是在同一個時間點,各個線程所看到的共享變量的值可能是不一致的。
有的觀點會將這種現象也視為重排序的一種,命名為“內存系統重排序”。
因為這種內存可見性問題造成的結果就好像是內存訪問指令發生了重排序一樣。
(執行了卻不知道執行了和以為執行了卻重排序沒有執行造成相同效果)
4,內存訪問重排序與Java內存模型
Java的目標是成為一門平台無關性的語言,即Write once, run anywhere. 但是不同硬件環境下指令重排序的規則不盡相同。 例如,x86下運行正常的Java程序在IA64下就可能得到非預期的運行結果。 為此,JSR-1337制定了Java內存模型(Java Memory Model, JMM),旨在提供一個統一的可參考的規范,屏蔽平台差異性。 從Java 5開始,Java內存模型成為Java語言規范的一部分。
根據Java內存模型中的規定,可以總結出以下幾條happens-before規則。
(ps:內存模型即通過運行環境把一些可見性和重排序問題統一成一個標准描述)
Happens-before的前后兩個操作不會被重排序且后者對前者的內存可見。
程序次序法則: 線程中的每個動作A都happens-before於該線程中的每一個動作B,其中,在程序中,所有的動作B都能出現在A之后。 監視器鎖法則: 對一個監視器鎖的解鎖 happens-before於每一個后續對同一監視器鎖的加鎖。 volatile變量法則:對volatile域的寫入操作happens-before於每一個后續對同一個域的讀寫操作。 線程啟動法則: 在一個線程里,對Thread.start的調用會happens-before於每個啟動線程的動作。 線程終結法則:線程中的任何動作都happens-before於其他線程檢測到這個線程已經終結、或者從Thread.join調用中成功返回,或Thread.isAlive返回false。 中斷法則: 一個線程調用另一個線程的interrupt happens-before於被中斷的線程發現中斷。 終結法則: 一個對象的構造函數的結束happens-before於這個對象finalizer的開始。 傳遞性: 如果A happens-before於B,且B happens-before於C,則A happens-before於C
Happens-before關系只是對Java內存模型的一種近似性的描述,它並不夠嚴謹,但便於日常程序開發參考使用,
關於更嚴謹的Java內存模型的定義和描述,請閱讀JSR-133原文或Java語言規范章節17.4。
除此之外,Java內存模型對volatile和final的語義做了擴展。
對volatile語義的擴展保證了volatile變量在一些情況下不會重排序,volatile的64位變量double和long的讀取和賦值操作都是原子的。
對final語義的擴展保證一個對象的構建方法結束前,所有final成員變量都必須完成初始化(前提是沒有this引用溢出)。
(ps:沒有理解final的意思)
Java內存模型關於重排序的規定,總結后如下表所示。(ps:下表沒看懂)
5,內存屏障
內存屏障(Memory Barrier,或有時叫做內存柵欄,Memory Fence)是一種CPU指令,用於控制特定條件下的重排序和內存可見性問題。
Java編譯器也會根據內存屏障的規則禁止重排序。
內存屏障可以被分為以下幾種類型:
LoadLoad 屏障:對於這樣的語句Load1; LoadLoad; Load2,在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
StoreStore屏障:對於這樣的語句Store1; StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
LoadStore 屏障:對於這樣的語句Load1; LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
StoreLoad 屏障:對於這樣的語句Store1; StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能。
有的處理器的重排序規則較嚴,無需內存屏障也能很好的工作,Java編譯器會在這種情況下不放置內存屏障。
為了實現上一章中討論的JSR-133的規定,Java編譯器會這樣使用內存屏障。(ps:下表沒看懂)