重排序定義
在前面我們提到過(guò),重排序是編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列重新排序的一種手段。但是我們也知道代碼不可能毫無(wú)原則的進(jìn)行重排序,如果是毫無(wú)原則的進(jìn)行重排序,那么我們的代碼將無(wú)法獲得預(yù)期的結(jié)果。因此重排序必須滿(mǎn)足如下原則:
- 在單線(xiàn)程中不改變運(yùn)行結(jié)果
- 操作不具備數(shù)據(jù)依賴(lài)性
那這兩條原則如何理解么,我們先來(lái)看看下面的定義
數(shù)據(jù)依賴(lài)性
數(shù)據(jù)依賴(lài)性的意思是,若果兩個(gè)操作訪(fǎng)問(wèn)同一個(gè)變量,并且其中一個(gè)操作是寫(xiě)操作,那么這兩個(gè)操作就具備數(shù)據(jù)依賴(lài)性。
as-if-serial語(yǔ)義
as-if-serial語(yǔ)義的意思是,不管如何重排序,在單線(xiàn)程中,程序的執(zhí)行結(jié)果不能被改變。編譯器、處理器和runtime都不行遵守as-if-serial語(yǔ)義。注意:as-if-serial只對(duì)單線(xiàn)程有效,對(duì)多線(xiàn)程無(wú)效。
了解了數(shù)據(jù)依賴(lài)性和as-if-serial后,我們看看如下示例:
int a = 1; // A
int b = 2; // B
int c = a * b; // C
在上述示例中,我們根據(jù)數(shù)據(jù)依賴(lài)性可以分析出,A和C具備數(shù)據(jù)依賴(lài)性,B和C具備依數(shù)據(jù)賴(lài)性,A和B之前不存在數(shù)據(jù)依賴(lài)性關(guān)系,因此編譯器和處理器在進(jìn)行重排序時(shí),A和B可以進(jìn)行重排序,但是A和C,以及B和C是不能進(jìn)行重排序的。因?yàn)楸M管A和B進(jìn)行了重排序,但是他們不影響程序在單線(xiàn)程中運(yùn)行的結(jié)果。
在這里,我們聯(lián)想前面講到過(guò)的happens-before,可以看出上面代碼存在3個(gè)happens-before關(guān)系。它們分別是:
- A happens-before B
- B happens-before C
- A happens-before C
在這里L(fēng)Z再次強(qiáng)調(diào),happens-before與時(shí)間上的先后完全沒(méi)有關(guān)系,happens-before僅僅要求,前一個(gè)操作的結(jié)果對(duì)后一個(gè)操作可見(jiàn)。在這里操作A的結(jié)果不需要對(duì)B可見(jiàn),重排序操作A和操作B的結(jié)果與按照A happens-before B 的執(zhí)行結(jié)果一致。在這種情況下JMM認(rèn)為這種重排序并不非法,JMM允許這種重排序。
重排序?qū)Χ嗑€(xiàn)程的影響
在了解了重排序的概率和數(shù)據(jù)依賴(lài)性以及as-if-serial的定以后,我們知道重排序在單線(xiàn)程中是沒(méi)有影響的,那么重排序?qū)Χ嗑€(xiàn)程是否有影響呢?我么首先來(lái)看看下面的代碼示例:
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
public void reader() {
if (flag) { // 3
int i = a * a; // 4
}
}
}
假設(shè)現(xiàn)在有A和B兩個(gè)下次,此時(shí)A線(xiàn)程執(zhí)行write()方法,B線(xiàn)程執(zhí)行reader()方法,那么在線(xiàn)程B在執(zhí)行操作4時(shí),能否獲取到a=1呢?答案是不一定。
由于操作1 和操作2沒(méi)有數(shù)據(jù)依賴(lài)性,因此編譯器和處理器能夠?qū)@2個(gè)操作進(jìn)行重排序,當(dāng)操作1和操作2進(jìn)行重排序時(shí),此時(shí)程序的執(zhí)行時(shí)序圖是這樣子的

如上圖所示,操作1和操作2進(jìn)行了重排序。程序在執(zhí)行時(shí),線(xiàn)程A首先寫(xiě)標(biāo)記變量flag,隨后線(xiàn)程B讀取這變量,由于條件判斷為真,線(xiàn)程B將讀取變量a,此時(shí)變量a還沒(méi)有被先A寫(xiě)入,在這里多線(xiàn)程程序的語(yǔ)義被重排序破壞掉了。
同樣,操作3和操作4也可以進(jìn)行重排序,但是操作3和操作4存在控制依賴(lài)的關(guān)系,即當(dāng)操作3為真時(shí),操作4才可以執(zhí)行。當(dāng)代碼中存在控制依賴(lài)關(guān)系時(shí),會(huì)影響指令序列執(zhí)行的并行度。為此,編譯器和處理器采用猜測(cè)執(zhí)行來(lái)克服控制相關(guān)性對(duì)并行度的影響。以處理器的猜測(cè)執(zhí)行為例,執(zhí)行線(xiàn)程B的處理器可以提前計(jì)算a*a,然后把計(jì)算的結(jié)果保存到重排序緩存中,當(dāng)操作3的結(jié)果為真時(shí),就把改計(jì)算結(jié)果寫(xiě)入變量i中。
通過(guò)上面的分析,我可以得出結(jié)論:重排序?qū)尉€(xiàn)程的執(zhí)行結(jié)果沒(méi)有影響,但是在多線(xiàn)程中,重排序可能會(huì)改變程序執(zhí)行的結(jié)果。