有序性(Ordering)是指在什么情況下一個處理器上運行的一個線程所執(zhí)行的 內(nèi)存訪問操作在另外一個處理器運行的其他線程看來是亂序的(Out of Order)。
亂序是指內(nèi)存訪問操作的順序看起來發(fā)生了變化。
在多核處理器的環(huán)境下,編寫的順序結(jié)構(gòu),這種操作執(zhí)行的順序可能是沒有保障的:
編譯器可能會改變兩個操作的先后順序;
處理器也可能不會按照目標(biāo)代碼的順序執(zhí)行;
這種一個處理器上執(zhí)行的多個操作,在其他處理器來看它的順序與目標(biāo)代碼指定的順序可能不一樣,這種現(xiàn)象稱為重排序。
重排序是對內(nèi)存訪問有序操作的一種優(yōu)化,可以在不影響單線程程序正確的情況下提升程序的性能.但是,可能 對多線程程序的正確性產(chǎn)生影響,即可能導(dǎo)致線程安全問題。
重排序與可見性問題類似,不是必然出現(xiàn)的。
與內(nèi)存操作順序有關(guān)的幾個概念:
源代碼順序, 就是源碼中指定的內(nèi)存訪問順序。
程序順序, 處理器上運行的目標(biāo)代碼所指定的內(nèi)存訪問順序。
執(zhí)行順序,內(nèi)存訪問操作在處理器上的實際執(zhí)行順序。
感知順序,給定處理器所感知到的該處理器及其他處理器的內(nèi)存訪問操作的順序。
可以把重排序分為指令重排序與存儲子系統(tǒng)重排序兩種:
指令重排序主要是由JIT編譯器,處理器引起的, 指程序順序與執(zhí)行順序不一樣。
存儲子系統(tǒng)重排序是由高速緩存,寫緩沖器引起的, 感知順序與執(zhí)行順序 不一致。
在源碼順序與程序順序不一致,或者 程序順序與執(zhí)行順序不一致的情況下,我們就說發(fā)生了指令重排序(Instruction Reorder)。
指令重排是一種動作,確實對指令的順序做了調(diào)整, 重排序的對象指令。
javac編譯器一般不會執(zhí)行指令重排序, 而JIT編譯器可能執(zhí)行指令重排序。
處理器也可能執(zhí)行指令重排序, 使得執(zhí)行順序與程序順序不一致。
指令重排不會對單線程程序的結(jié)果正確性產(chǎn)生影響,可能導(dǎo)致多線程程序出現(xiàn)非預(yù)期的結(jié)果。
存儲子系統(tǒng)是指寫緩沖器與高速緩存。
高速緩存(Cache)是CPU中為了匹配與主內(nèi)存處理速度不匹配而設(shè)計的一個高速緩存。
寫緩沖器(Store buffer, Write buffer)用來提高寫高速緩存操作的效率。
即使處理器嚴(yán)格按照程序順序執(zhí)行兩個內(nèi)存訪問操作,在存儲子系統(tǒng)的作用下, 其他處理器對這兩個操作的感知順序與程序順序不一致,即這兩個操作的順序順序看起來像是發(fā)生了變化, 這種現(xiàn)象稱為存儲子系統(tǒng)重排序。
存儲子系統(tǒng)重排序并沒有真正的對指令執(zhí)行順序進行調(diào)整,而是造成一種指令執(zhí)行順序被調(diào)整的現(xiàn)象。
存儲子系統(tǒng)重排序?qū)ο笫莾?nèi)存操作的結(jié)果。
從處理器角度來看, 讀內(nèi)存就是從指定的RAM地址中加載數(shù)據(jù)到寄存器,稱為Load操作; 寫內(nèi)存就是把數(shù)據(jù)存儲到指定的地址表示的RAM存儲單元中,稱為Store操作.內(nèi)存重排序有以下四種可能:
● LoadLoad重排序,一個處理器先后執(zhí)行兩個讀操作L1和L2,其他處理器對兩個內(nèi)存操作的感知順序可能是L2->L1。
● StoreStore重排序,一個處理器先后執(zhí)行兩個寫操作W1和W2,其他處理器對兩個內(nèi)存操作的感知順序可能是W2->W1。
● LoadStore重排序,一個處理器先執(zhí)行讀內(nèi)存操作L1再執(zhí)行寫內(nèi)存操作W1, 其他處理器對兩個內(nèi)存操作的感知順序可能是W1->L1。
● StoreLoad重排序,一個處理器先執(zhí)行寫內(nèi)存操作W1再執(zhí)行讀內(nèi)存操作L1, 其他處理器對兩個內(nèi)存操作的感知順序可能是L1->W1。
內(nèi)存重排序與具體的處理器微架構(gòu)有關(guān),不同架構(gòu)的處理器所允許的內(nèi)存重排序不同。
內(nèi)存重排序可能會導(dǎo)致線程安全問題.假設(shè)有兩個共享變量int data = 0; boolean ready = false;
處理器1 |
處理器2 |
data = 1; //S1 ready = true; //S2 |
|
|
while( !ready){} //L3 sout( data ); //L4 |
JIT編譯器,處理器,存儲子系統(tǒng)是按照一定的規(guī)則對指令,內(nèi)存操作的結(jié)果進行重排序, 給單線程程序造成一種假象----指令是按照源碼的順序執(zhí)行的.這種假象稱為貌似串行語義. 并不能保證多線程環(huán)境程序的正確性。
為了保證貌似串行語義,有數(shù)據(jù)依賴關(guān)系的語句不會被重排序,只有不存在數(shù)據(jù)依賴關(guān)系的語句才會被重排序.如果兩個操作(指令)訪問同一個變量,且其中一個操作(指令)為寫操作,那么這兩個操作之間就存在數(shù)據(jù)依賴關(guān)系(Data dependency)。
如:
x = 1; y = x + 1; 后一條語句的操作數(shù)包含前一條語句的執(zhí)行結(jié)果;
y = x; x = 1; 先讀取x變量,再更新x變量的值;
x = 1; x = 2; 兩條語句同時對一個變量進行寫操作
如果不存在數(shù)據(jù)依賴關(guān)系則可能重排序,如:
double price = 45.8;
int quantity = 10;
double sum = price * quantity;
存在控制依賴關(guān)系的語句允許重排.一條語句(指令)的執(zhí)行結(jié)果會決定另一條語句(指令)能否被執(zhí)行,這兩條語句(指令)存在控制依賴關(guān)系(Control Dependency). 如在if語句中允許重排,可能存在處理器先執(zhí)行if代碼塊,再判斷if條件是否成立。
可以使用volatile關(guān)鍵字, synchronized關(guān)鍵字實現(xiàn)有序性。