歡迎加入技術交流羣186233599討論交流,也歡迎關注筆者公衆號:風火說。html
這部分的內容比較抽象,首先是一開始的定義,以下java
紅色下劃線的內容應該是理解的關鍵。首先,E 是一個特定的執行序列,其由指令集合 A 以及用於對集合 A 內部存在的 PO,SO,SW,HB 排序構成。Ci 是被 E 包含的一個子集,也就是說 Ci 中的指令所有都在執行 E 的指令集合 A 中存在。程序員
來看第二,第三和第四個紅線(忽略A是無限集合的狀況,無限集合意味着線程出現了死循環,永不終止,這並非一個合理的程序),這三者合在一塊兒理解,能夠認爲是 Ci 增長一個指令,就構成了 Ci+1,也就造成了新的 Ai+1 。而新的 Ai+1 結合 PO,SO,SW,HB 關係就成爲了新的 Ei+1 。spa
接着來看後續的定義線程
這 5 個約束合在一塊兒其實是說明如何構成一個 C 集合。簡單而言,Ci 是 Ai的一個子集,而且這個子集和執行軌跡Ei 擁有相同的 HB,SO 關係,且 Ci 中寫入操做的寫入值和 Ei 中相同,Ci 中對寫入值的觀察結果和 Ei 中相同。而 Ei 是逐步構成 E 的第 i 步驟,最終 E~n~ 等同於 E 。這實際上約束了 C 是如何構成的,它並非憑空而來,而是不斷的將 E 中的指令添加到 C 之中,而且這些添加的指令都和 E 擁有相關的觀察效果,寫入值,以及偏序關係。經過確保一系列的 Ei 都是合法的,最終肯定 E 是合法的。code
再來剩下的兩條規則cdn
第 6 條規則定義了要往集合 C 中添加讀取指令時,該指令的觀察結果。換句話說集合 C~i-1~ 中的寫入操做產生的效果,能被任意未添加到該集合中的寫入操做觀察到。htm
第 7 條規則和上面的 5 條規則相同,也是在明確在集合 C 中產生的觀察效果在執行軌跡 E 中也是存在的。blog
7個規則都在描述在集合 C 中的寫入值,讀取結果,指令排列順序都是和 E 等同,所以經過不斷的構建 Ci ,最終 C~n~ 等同於 A~n~ ,再加上在 C~n~ 中的寫入值,讀取結果,指令排列順序,就構成了最終的 E 。而若是這一系列的 Ci 都是「合法」的話,則最終的執行軌跡 E 也是合法的。排序
當咱們須要向集合新增一個讀取指令時,其讀取到的值只能在該集合中的寫入值。提交指令到集合中時,若是存在HB 關係 或依賴關係的語句阻止其提交,則提交不能成功。
首先來看一段代碼,以下
nonvolatile global int a = 0, b = 0;
ThreadA()
{
int aL = a;
if(aL == 1) b = 1;
}
ThreadB()
{
int bL = b;
if(bL == 1) a = 1;
}
複製代碼
對於內存模型而言,其只關注操做內存的指令,在執行軌跡 E 的 A 集合的內容是
int aL = a;
b = 1;
bL = b;
a = 1;
複製代碼
因爲int aL = a;
和a = 1;
不存在 HB 關係,所以能夠經過數據競爭的方式讀取到該寫入值,也就是aL
的值是 1 。bL = b;
和b = 1;
不存在 HB 關係,所以能夠經過數據競爭的方式讀取到該寫入值,也就是bL
的值是 1 。
一個讀取操做能夠讀取的到值或者是經過 HB 關係獲得,或者是經過數據競爭獲得。也就是說,在沒有 HB 關係阻止該讀取結果時,該讀取結果是容許的,這被稱之爲 HB 一致性。顯然,上面的輸出結果aL==bL==1
是符合 HB 一致性的。
然而從順序一致性執行的角度而言,這種輸出結果就好像是由於aL
讀取到線程 B 寫入的值,產生了b=1
的結果,而這個結果致使了bL==1
的結果,而這個結果致使了a=1
的結果,而a==1
致使了aL=1
。造成了一個循環,顯然這是違背直覺的。而 JMM 中因果性的要求就是用來斷定這種執行軌跡是否合法的依據。
再用因果性分析這個執行以前,咱們先看另一個更簡單一些的例子,代碼以下
0: x == y == 0
Thread 1:
1: r1 = x;
2: y = 1;
Thread 2:
3: r2 = y;
4: x = r2;
複製代碼
這個例子反覆出現,顯然咱們知道r1==r2==1
是一個合法的輸出結果。由於重排序的緣由,y=1
被執行,然後r2=y
觀察到這個寫入,x=r2
一樣獲得值 1,r1=x
觀察到這個寫入。下面咱們使用因果性來分析這個執行軌跡。
首先咱們將集合 C 中添加指令 2 。與指令 2 存在 HB 關係的是指令 0 和 1 。他們都不會阻止指令 2 的發生,所以指令 2 被容許添加到集合 C 中,此時有 C1 。
咱們用 W(variable,value) 來表達對一個變量 variable 寫入 value 的值,用 R(variable,value) 表達從變量 variable 中讀取到 value 的值。
所以目前咱們有 C0= {W(x,0),W(y,0)} 的初始狀態。而 C1=C0 U {W(y,1)} 。而後咱們添加指令 3 到 C1 中,按照 HB 關係,指令觀察到的值應該是指令 0 寫入的。可是同時,它也容許觀察到提交集合中已經寫入的值,也就是存在於提交集合中指令 2 的寫入值。所以咱們有 C2=C1 U {R(y,1)} 。
接着咱們提交指令 4 ,顯然此時有 C3=C2 U {W(x,1)} 。
最後咱們提交指令 1,按照 HB 關係,此時容許的觀察值由指令 0 寫入,也就是 0 。與上述相同,容許其觀察到在提交集合中的寫入值,所以 C4=C3 U {R(x,1)} 。
C4=A4,C4 中的寫入值,讀取值,排列順序都與 E4 相同,也與 E 相同。所以斷定該執行軌跡是合法的,其表現是符合 JMM 要求的。
接下來咱們回到最開始的例子,若是咱們要獲得bL==1
的結果,意味着咱們須要執行b=1
這個指令。而要執行該指令,咱們須要執行int aL = a;
指令而且讀取到值 1 。注意,因果性的判斷是須要考慮條件判斷因素的,而 HB 一致性則不考慮,它僅僅是提取全部的可能執行指令而且假定其執行。
從提交集合的角度出發,咱們須要提交int aL = a;
其實是想提交 R(a,1) 。可是 C0={W(x,0),W(y,0)} ,C1 中的讀取操做的讀取值只能由 C0 中的寫入形成,所以 R(a,1) 沒法被提交到 C1 中。這就意味着達成a==b==1
的提交集合不合法,所以對應的執行軌跡也是非法的。因此這種結果不被 JMM 容許。
int /* non-volatile */ a;
int /* non-volatile */ b;
ThreadA()
{
int tmp = a;
b = tmp;
}
ThreadB()
{
int tmp = b;
a = tmp;
}
複製代碼
對於上面的代碼,a==b==1
的結果是符合 HB 一致性的。看起來這個比例子 1 中出現的狀況更費解一些,由於 1 這個值彷佛是無中生有的。可是咱們首先假設int tmp = a;
讀取到了 1 ,此時b = tmp;
將會寫入 1 。而int tmp = b;
又讀取到了該值,這會致使a = tmp;
寫入值 1 ,偏偏知足了int tmp = a;
讀取到 1 的須要。HB 一致性中,若是沒有 HB 關係阻止一個值被讀取,則該讀取都是被容許的,也就是能夠認爲int tmp = a;
經過數據競爭的方式讀取到了將來的寫入值。按照這種方式考慮,這個例子實際上相似於例子1中的第二個示例。
可是顯然,這個結果是違反直覺的,其結果也被 JMM 禁止。咱們使用因果性的方式進行分析。
首先,顯然咱們有 C0={W(a,0),W(b,0)} 。接着咱們提交int tmp = a;
(不能先提交b = tmp;
是由於int tmp = a;
與其存在依賴關係,且有 HB 關係)。根據 C0 的內容,顯然此時只容許提交 R(a,0) 或 R(b,0) 。經過因果性分析,咱們獲得這個例子的合法輸出只能是a==b==1
。
非正式的說,能夠認爲經過結合 HB 一致性和因果性要求獲得 JMM 。這兩個約束結合在一塊兒,纔有了 JMM 對程序員的保證:若是一個程序是正確同步的,其程序表現爲順序一致性。