移步java多線程系列文章
重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行重新排序的一種手段重挑。
1 數(shù)據(jù)依賴性
- 如果兩個(gè)操作訪問同一個(gè)變量囚似,且這兩個(gè)操作中有一個(gè)為寫操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)依賴性顾画。
-
數(shù)據(jù)依賴分為下列3種類型
上面3種情況翎迁,只要重排序兩個(gè)操作的執(zhí)行順序,程序的執(zhí)行結(jié)果就會(huì)被改變叨咖。
- 編譯器和處理器可能會(huì)對(duì)操作做重排序。
- 編譯器和處理器在重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴性甸各,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴關(guān)系的兩個(gè)操作的執(zhí)行順序垛贤。
- 這里所說的數(shù)據(jù)依賴性僅針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器考慮趣倾。
2 as-if-serial語(yǔ)義
as-if-serial語(yǔ)義的意思是:不管怎么重排序(編譯器和處理器為了提高并行度)聘惦,(單線程)程序的執(zhí)行結(jié)果不能被改變。編譯器儒恋、runtime和處理器都必須遵守as-if-serial語(yǔ)義善绎。
- 為了遵守as-if-serial語(yǔ)義,編譯器和處理器不會(huì)對(duì)存在數(shù)據(jù)依賴關(guān)系的操作做重排序诫尽,因?yàn)檫@種重排序會(huì)改變執(zhí)行結(jié)果禀酱。但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系牧嫉,這些操作就可能被編譯器和處理器重排序剂跟。
- as-if-serial語(yǔ)義把單線程程序保護(hù)了起來(lái),遵守as-if-serial語(yǔ)義的編譯器酣藻、runtime和處理器共同為編寫單線程程序的程序員創(chuàng)建了一個(gè)幻覺:?jiǎn)尉€程程序是按程序的順序來(lái)執(zhí)行的浩聋。as-if-serial語(yǔ)義使單線程程序員無(wú)需擔(dān)心重排序會(huì)干擾他們,也無(wú)需擔(dān)心內(nèi)存可見性問題臊恋。
3 程序順序規(guī)則
示例--計(jì)算圓面積
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C
依賴關(guān)系如圖
- A和C之間存在數(shù)據(jù)依賴關(guān)系,同時(shí)B和C之間也存在數(shù)據(jù)依賴關(guān)系墓捻。因此在最終執(zhí)行的指令序列中抖仅,C不能被重排序到A和B的前面(C排到A和B的前面,程序的結(jié)果將會(huì)被改變)砖第。
- 但A和B之間沒有數(shù)據(jù)依賴關(guān)系撤卢,編譯器和處理器可以重排序A和B之間的執(zhí)行順序。
- 如果A happens-before B梧兼,JMM并不要求A一定要在B之前執(zhí)行放吩。
- JMM僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見,且前一個(gè)操作按順序排在第二個(gè)操作之前羽杰。
- 這里操作A的執(zhí)行結(jié)果不需要對(duì)操作B可見渡紫;而且重排序操作A和操作B后的執(zhí)行結(jié)果,與操作A和操作B按happens-before順序執(zhí)行的結(jié)果一致考赛。在這種情況下惕澎,JMM會(huì)認(rèn)為這種重排序并不非法(not illegal),JMM允許這種重排序颜骤。
4 重排序?qū)Χ嗑€程的影響
示例
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
Public void reader() {
if (f?lag) { // 3
int i = a * a; // 4
……
}
}
}
flag變量是個(gè)標(biāo)記唧喉,用來(lái)標(biāo)識(shí)變量a是否已被寫入。
這里假設(shè)有兩個(gè)線程A和B,A首先執(zhí)行writer()方法八孝,隨后B線程接著執(zhí)行reader()方法董朝。線程B在執(zhí)行操作4時(shí),能否看到線程A在操作1對(duì)共享變量a的寫入呢干跛?
答案是:不一定能看到子姜。
由于操作1和操作2沒有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以對(duì)這兩個(gè)操作重排序驯鳖;
同樣闲询,操作3和操作4沒有數(shù)據(jù)依賴關(guān)系,編譯器和處理器也可以對(duì)這兩個(gè)操作重排序浅辙。
-
當(dāng)操作1和操作2重排序時(shí)
程序執(zhí)行時(shí)扭弧,線程A首先寫標(biāo)記變量flag,隨后線程B讀這個(gè)變量记舆。由于條件判斷為真鸽捻,線程B將讀取變量a。此時(shí)泽腮,變量a還沒有被線程A寫入御蒲,在這里多線程程序的語(yǔ)義被重排序破壞了!
-
當(dāng)操作3和操作4重排序時(shí)
操作3和操作4存在控制依賴關(guān)系诊赊。 當(dāng)代碼中存在控制依賴性時(shí)厚满,會(huì)影響指令序列執(zhí)行的并行度。為此碧磅,編譯器和處理器會(huì)采用猜測(cè)(Speculation)執(zhí)行來(lái)克服控制相關(guān)性對(duì)并行度的影響碘箍。以處理器的猜測(cè)執(zhí)行為例,執(zhí)行線程B的處理器可以提前讀取并計(jì)算a*a鲸郊,然后把計(jì)算結(jié)果臨時(shí)保存到一個(gè)名為重排序緩沖(Reorder Buffer丰榴,ROB)的硬件緩存中。當(dāng)操作3的條件判斷為真時(shí)秆撮,就把該計(jì)算結(jié)果寫入變量i中四濒。猜測(cè)執(zhí)行實(shí)質(zhì)上對(duì)操作3和4做了重排序。重排序在這里破壞了多線程程序的語(yǔ)義职辨!
在單線程程序中盗蟆,對(duì)存在控制依賴的操作重排序,不會(huì)改變執(zhí)行結(jié)果(這也是as-if-serial語(yǔ)義允許對(duì)存在控制依賴的操作做重排序的原因)舒裤;
但在多線程程序中姆涩,對(duì)存在控制依賴的操作重排序,可能會(huì)改變程序的執(zhí)行結(jié)果惭每。
參考
《java并發(fā)編程的藝術(shù)》