指令重排
指令重排(HappenBefore): 執行代碼的順序可能和編寫代碼的順序不一致,即虛擬機優化代碼順序,則爲指令重排。即編譯器或運行時環境爲了優化程序性能而採取的對指令進行重新排序執行的一種手段。
-
在虛擬機層面:爲了儘可能的減少內存操作速度遠遠慢於cpu執行速度所帶來的cpu空閒的影響,虛擬機會按照自己的一些規則將程序編寫順序打亂,即先寫的可能後執行,後寫的可能先執行。
-
在硬件層面,cpu會將接受到的一批指令按照其規則重排序,同樣是基於cpu速度比緩存速度快的原因,和上面的目的相似。只是cpu每次只能在有限的範圍類重排,而虛擬機可以在更大層面,更多指令範圍內重排。
例如在機器中,代碼的執行分4步:
- 獲取指令
- 解碼,翻譯指令,從寄存器中取值
- 操作
- 將結果寫回寄存器中
如:subTotal = price + fee;
total +=subTotal;
isDone = true;
price + fee這條語句在計算的過程可能比較慢,在結果寫回之前發現isDone和計算過程毫不相干,爲了提升性能,就先執行了isDone=true;等subTotal寫回後再執行total+=subTotal語句。這在單線程中是沒問題的。但在多線程,如果包含isDone的條件語句中有修改total的值的操作,結果就會發生變化。
數據依賴 如果兩個操作訪問同一個變量,且這兩個操作中一個爲寫操作,此時這兩個操作之間就存在數據依賴。
數據依賴分爲三種類型:
- 先寫後讀 a=2;b=a;
- 先讀後寫 a=b;b=2;
- 先寫後寫 a=2;a=1;
上面三種情況中,只要重排了兩個操作的順序,程序執行的結果將會改變。
而編譯器和處理器不會改變存在數據依賴關係的兩個操作的執行順序。
這裏是一個java程序實例:
package Ohter;
/**
* 指令重排:代碼執行順序與預期不一致
* 目的:提高性能
* @author CR553
*
*/
public class HappenBefore {
public static int a=0;
public static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++)
{
a=0;
flag=false;
//更改數據
Thread t1=new Thread(()->{
a=1;
flag=true;
});
//讀取數據
Thread t2=new Thread(()-> {
if(flag)//按道理講如果這條語句執行了,就不會執行下面的輸出語句了
{
a*=1;
}
//指令重排發生了!jvm沒有等待a*=1;運行完就開始運行下一個語句,導致輸出了a的值
if(a==0)
{
System.out.println("happenBefore a-->"+a);//a的值可能爲0或1 爲0好理解;爲1的原因是在執行if(a==0)結束後a*=1才結束了,導致輸出的是1
}
});
t1.start();
t2.start();
t1.join();//保證數據一定先在讀取前修改
t2.join();
}
}
}