Java併發(十三)----共享存在的問題

1、小故事

  • 老王(操作系統)有一個功能強大的算盤(CPU),現在想把它租出去,賺一點外快

  • 小南、小女(不同的線程)來使用這個算盤來進行一些計算,並按照時間給老王支付費用

  • 但小南不能一天24小時使用算盤,他經常要小憩一會(sleep),又或是去喫飯上廁所(阻塞 io 操作),有時還需要一根菸,沒煙時思路全無(wait)這些情況統稱爲(阻塞)

  • 在這些時候,算盤沒利用起來(不能收錢了),老王覺得有點不划算

  • 另外,小女也想用用算盤,如果總是小南佔着算盤,讓小女覺得不公平

  • 於是,老王靈機一動,想了個辦法 [ 讓他們每人用一會,輪流使用算盤 ]

  • 這樣,當小南阻塞的時候,算盤可以分給小女使用,不會浪費,反之亦然

  • 最近執行的計算比較複雜,需要存儲一些中間結果,而學生們的腦容量(工作內存)不夠,所以老王申請了一個筆記本(主存),把一些中間結果先記在本上

  • 計算流程是這樣的

  • 但是由於分時系統,有一天還是發生了事故

  • 小南剛讀取了初始值 0 做了個 +1 運算,還沒來得及寫回結果

  • 老王說 [ 小南,你的時間到了,該別人了,記住結果走吧 ],於是小南唸叨着 [ 結果是1,結果是1...] 不甘心地到一邊待着去了(上下文切換)

  • 老王說 [ 小女,該你了 ],小女看到了筆記本上還寫着 0 做了一個 -1 運算,將結果 -1 寫入筆記本

  • 這時小女的時間也用完了,老王又叫醒了小南:[小南,把你上次的題目算完吧],小南將他腦海中的結果 1 寫入了筆記本

  • 小南和小女都覺得自己沒做錯,但筆記本里的結果是 1 而不是 0

2、Java 的體現

兩個線程對初始值爲 0 的靜態變量一個做自增,一個做自減,各做 5000 次,結果是 0 嗎?

static int counter = 0;
​
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter++;
        }
    }, "t1");
​
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter--;
        }
    }, "t2");
​
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("{}",counter);
}

多做幾次實驗,可以得出答案明顯不全是0。

3、問題分析

以上的結果可能是正數、負數、零。爲什麼呢?因爲 Java 中對靜態變量的自增,自減並不是原子操作,要徹底理解,必須從字節碼來進行分析

例如對於 i++ 而言(i 爲靜態變量),實際會產生如下的 JVM 字節碼指令:

getstatic     i  // 獲取靜態變量i的值
iconst_1         // 準備常量1
iadd             // 自增
putstatic     i  // 將修改後的值存入靜態變量i

而對應 i-- 也是類似:

getstatic     i  // 獲取靜態變量i的值
iconst_1         // 準備常量1
isub             // 自減
putstatic     i  // 將修改後的值存入靜態變量i

而 Java 的內存模型如下,完成靜態變量的自增,自減需要在主存和工作內存中進行數據交換: 

如果是單線程以上 8 行代碼是順序執行(不會交錯)沒有問題:

但多線程下這 8 行代碼可能交錯運行:

出現負數的情況:

出現正數的情況:
 
 

4、臨界區 Critical Section

  • 一個程序運行多個線程本身是沒有問題的

  • 問題出在多個線程訪問共享資源

    • 多個線程讀共享資源其實也沒有問題

    • 在多個線程對共享資源讀寫操作時發生指令交錯,就會出現問題

  • 一段代碼塊內如果存在對共享資源的多線程讀寫操作,稱這段代碼塊爲臨界區

例如,下面代碼中的臨界區

static int counter = 0;
​
static void increment() 
// 臨界區
{    
    counter++;
}
​
static void decrement() 
// 臨界區
{    
    counter--;
}

5、競態條件 Race Condition

多個線程在臨界區內執行,由於代碼的執行序列不同而導致結果無法預測,稱之爲發生了競態條件

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章