1. 程式人生 > 其它 >6.22Java多執行緒happenbefore

6.22Java多執行緒happenbefore

6.22Java多執行緒happenbefore

編譯器和CPU會嘗試重排指令使得程式碼更快地執行

發生情況:

  • 程式碼間沒有直接聯絡,沒有依賴

這樣就會發生指令重排a

步驟
  • 拿到指令,進行編譯,放入暫存器--->fetch

  • 解碼指令,從暫存器中拿值,從主存拷貝到工作記憶體--->copy

  • 執行選項--->excutor operation

  • 將結果同步到主存當中--->write back

步驟如圖:

以上是我們的高階語言在彙編層面的展示

第二條是求和指令,第三條是賦值指令。有可能求和比賦值慢此時CPU檢視到賦值指令比較快且和本次求和指令無關聯就會把下面的賦值指令提前到求和指令這裡。此時求和指令的計算結果還沒有寫回到暫存器當中有可能後面條件為isDone的指令就已經運行了。

在多執行緒環境下有可能我們在操作total,那麼此時的值可能就不是我們需要的total了。所以在多執行緒環境下指令重排會對執行緒操作結果有影響。

什麼是指令重排?
  • 執行程式碼的順序可能與編寫程式碼不一致--->虛擬機器優化程式碼順序,則為指令重排happen-before

  • 編譯器或執行時環境為了優化程式效能而採取的對指令進行重新排序執行的一種手段

HappenBefore會發生在兩個層面

指令重排在虛擬機器層面的表現:

目的:

儘可能減少記憶體操作速度遠慢於CPU執行速度所帶來地CPU空置的影響

前提:

後面的指令先於前面執行不會產生錯誤(產生什麼錯誤?)

過程:

寫在前面的程式碼先執行,當效率較慢時,後面的程式碼先於前面的程式碼開始執行,並且先於前面的程式碼執行結束。寫在後面的程式碼存在一些情況下先於前面的結束。

指令重排在硬體層面的表現:

CPU會接收到一批指令按照其規則重排序,基於CPU速度比快取速度快的原因,只是硬體處理,每次能接收到有限指令範圍內重排序,虛擬機器可以在更大層面、更多指令範圍內重排序。

指令重排發生的前提

資料之間沒有依賴:

什麼是資料依賴?

如果兩個操作訪問同一個變數,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在資料依賴。

分類:

名稱程式碼示例說明
寫後讀 a = 1;b = a; 寫一個變數之後再讀這個變數(變數之間的賦值不是寫而是讀--->一個區域拷貝到另一個區域)
寫後寫 a = 1;a = 2; 寫一個變數之後再寫這個變數(兩次賦值操作)
讀後寫 a = b;b = 1; 讀一個變數之後再寫這個變數(賦值--->寫)

以上三種情況,如果指令重排,程式的執行結果將會被改變。所以編譯器何處理器不會改變存在資料依賴關係的兩個操作的執行順序。

Java指令重排例項demo
package thread.rearrangement;

/**
* 指令重排:程式碼的執行順序與預期的不一致
* 目的:提高效能
* @since JDK 1.8
* @date 2021/6/22
* @author Lucifer
*/
public class HappenBeforeNo1 {

/*新增兩個無關聯的變數*/
private static int a = 0;

private static boolean flag = false;

public static void main(String[] args) throws InterruptedException {

/*
建立兩個執行緒:
1、一個對int進行修改
2、另一個對int進行讀取
*/

for (int i=0; i<10; i++){

/*每次都要初始化*/
a = 0;
flag = false;

//執行緒一:更改資料--->使用Lambda表示式
Thread t = new Thread(() ->{
a = 1;
flag = true;
});

//執行緒二:讀取資料
Thread t2 = new Thread(() -> {
/*判斷flag的值*/
if (flag){
a *= 1;
}

/*
這一塊會存在指令重排
a的值沒有寫入主存當中,所以執行了下面if(a==0)的結果
*/

if (a==0){
System.out.println("Happen Before a->"+a);
}
});
/*
因為如果已經更改好了,那麼flag就為真了a怎麼樣都是1
那麼a==0就不會發生
*/

t.start();
t2.start();

//執行緒插隊--->合併執行緒
t.join();
t2.join();

}
}
}

*解析圖: