重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行重新排序的一種手段
- 在程序執(zhí)行時(shí),為了提高性能,編譯器和處理器通常會(huì)對(duì)指令進(jìn)行重排序,但是不能隨意的重排序,必須滿(mǎn)足一下兩點(diǎn)
- 單線程情況下,不能改變程序執(zhí)行結(jié)果
- 存在數(shù)據(jù)依賴(lài)性關(guān)系的操作之間不能重排序
數(shù)據(jù)的依賴(lài)性
- 如果兩個(gè)操作訪問(wèn)同一個(gè)共享變量,且這兩個(gè)操作有一個(gè)是寫(xiě)操作,那么這兩個(gè)操作之間就存在數(shù)據(jù)依賴(lài)性
- 編譯器和處理器會(huì)在重排序時(shí),遵守?cái)?shù)據(jù)依賴(lài)性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴(lài)性的兩個(gè)操作的執(zhí)行順序
- 這里的數(shù)據(jù)依賴(lài)性也只是針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴(lài)性不被編譯器和處理器考慮
as-if-serial語(yǔ)義
- as-if-serial語(yǔ)義的意思是,不管怎么重排序(編譯器和處理器為了提高并行度),單線程程序的執(zhí)行結(jié)果不能改變。編譯器、runtime和處理器都必須遵守as-if-serial語(yǔ)義
- 為了遵守as-if-serial語(yǔ)義,編譯器和處理器不會(huì)對(duì)存在數(shù)據(jù)依賴(lài)性的操作重排序,因?yàn)橐坏┲嘏判颍涂赡軙?huì)影響程序的執(zhí)行結(jié)果,對(duì)于不存在數(shù)據(jù)依賴(lài)性的操作,是可能會(huì)被編譯器和處理器做重排序處理
int a = 1; //A
int b = 2; //B
int c = a + b; //C
- 上面的數(shù)據(jù)依賴(lài)性關(guān)系是,C依賴(lài)于A和B,而A和B之間不存在數(shù)據(jù)依賴(lài)性,因此編譯器可以對(duì)A和B進(jìn)行重排序
- as-if-serial語(yǔ)義給我們一個(gè)錯(cuò)覺(jué),遵守as-if-serial語(yǔ)義的編譯器和處理器,在單線程的情況下,程序是按照順序執(zhí)行,其實(shí),對(duì)于不存在數(shù)據(jù)依賴(lài)性的操作,編譯器和處理器可以隨意的對(duì)其進(jìn)行重排序操作,以提高執(zhí)行效率
程序順序規(guī)則
- 根據(jù)happens-before的程序規(guī)則,上面的例子中
- A happens-before B
- B happens-before C
- A happens-before C
- 這里的A happens-before C是根據(jù)happens-before的傳遞性推導(dǎo)出來(lái)的
重排序?qū)Χ嗑€程的影響
- 如下代碼塊
class Example {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
public void reader() {
if (flaf) { // 3
int i = a + a; // 4
}
}
}
- falg變量是一個(gè)標(biāo)記,用來(lái)標(biāo)記變量a是否已被寫(xiě)入,假如有線程A和線程B,A先執(zhí)行writer,B執(zhí)行reader,線程B在執(zhí)行操作4時(shí), 能否看到線程A在操作1對(duì)共享變量a的寫(xiě)入呢?答案是不一定能看到,由于1和2之間沒(méi)有數(shù)據(jù)依賴(lài)性,編譯器和處理器可以對(duì)這兩個(gè)操作進(jìn)行重排序,二3和4之間沒(méi)有數(shù)據(jù)依賴(lài)性,編譯器和處理器也可以對(duì)這兩個(gè)操作進(jìn)行重排序,當(dāng)1和2進(jìn)行重排序時(shí),線程B執(zhí)行的結(jié)果就處于一種待定的情況了
參考
周志明 《深入理解Java虛擬機(jī)》
方騰飛 《Java并發(fā)編程的藝術(shù)》
寫(xiě)在最后
我從未感覺(jué)如此強(qiáng)烈,我的靈魂離我如此遙遠(yuǎn),而且的存在卻如此的真實(shí)