Java線程中的問題——競態條件、數據競爭

1.競態條件

說得通俗一點,就是線程A 需要判斷一個變量的狀態,然後根據這個變量的狀態來執行某個操作。在執行這個操作之前,這個變量的狀態可能會被其他線程修改。

典型情況:

(1)check-then-act

if(a == 10.0)
{
    b = a / 2.0;
}

在單線程的環境下,不會出現線程安全問題。

在多線程環境下,如果a和b是局部變量,也不會出現線程安全問題,因爲每個線程都會有自己的局部變量拷貝。

在多線程環境下,若a、b是實例變量或者類變量,同時有兩個線程訪問這段程序。假設一條線程已經判斷完 if(a == 10.0)滿足條件,即將執行 b = a / 2.0,這時被調度器暫停了。同時調度器恢復了另一條線程,並且該線程改變了a的值。當上一條線程恢復,執行 b = a / 2.0 時,a值已經改變。

(2)read - modify - write

複合操作,自增

public int getID()
{
   return counter++;
}

counter++是三個獨立的操作:讀取counter值;完成counter+1;將更新後的結果存儲在counter中。

假設線程1讀取了counter = 1,此時被調度器暫停,線程2讀取counter = 1,對counter進行加1,將結果2存儲進counter,並將1返回給調用者(因爲i++表示的是先賦值後加1,所以返回的是1)。這時,線程1又恢復過來了,執行加1,將結果2存儲進counter,然後將1返回給調用者。這樣的後果就是線程1覆蓋了線程2的動作,我們就會錯過一次遞增並生成了一個重複的ID。

——摘自JAVA併發編程實戰
在實際情況中經常會遇到競態條件*例如,假定你計劃中午在University Avenue的星巴克與一位朋友會面。但當你到達那裏時,發現在University Avenue上有兩家星巴克,並且你不知道是哪一家。在12:00時,你沒有在星巴克A看到朋友,那麼就會去星巴B看看他是否在那裏但他也不在那裏。這有幾種可能:你的朋友遲到了,還沒到仵何一家星巴克;你的朋友在你離開後到星巴克A ; 你的朋友在星巴克B,但他去星巴克A找你,並且 此時正在去星巴克A的途中。假設是最糟糕的情況,即最後一種可能.現在是你們兩個都去過了兩家星巴究,且開始懷疑對方是否失約了.現在你會怎麼做? 到另一家 星巴克?來來回回要走名少次?除非你們之間約定了其種協議,否則你們整天都在University Avenue上走來走去,倍感沮喪。在“我去看看他是否在另一家星巴克”這種方法中,問題在於:當你在街上走時,你的朋 友可能已經離開了你要去的星巴克。你首先看看星巴克A,發現“他不在”,並且開始去找他。你可以在星巴克B屮做同樣的選擇,但不是同時發生。兩家星巴克之間有幾分鐘的路程, 而就在這幾分鐘的時間裏,系統的狀態可能會發生變化。
在星巴克這個示例中說明了一種競態條件.因爲要獲得正確的結果(與朋友會面),必須 取決於亊件的發生時序(當你們到達星巴克時,在離開並去另一家星巴克之前會等待多長時 間……)。當你邁出前門時,你在星巴克A的觀察結果將變得無效,你的朋友可能從後門進來 了,而你卻不知道。這種觀察結果的失效就是大多數競態條件的本質一於一種可能失效的 觀察結果來做出判斷或者執行某個計算。這種類型的競態條件稱爲“先檢査後執行”:首先觀 察到某個條件爲真(例如文件X不存在)•然後根據這個觀察結果採用相應的動作(創建文件 X),但事實上,在你觀察到這個結果以及開始創建文件之間,觀察結果可能變得無效(另一 個線程在這期間創建了文件X),從而導致各種問題(未預期的異常、數據被覆蓋、文件被破壞等)。
--------------------- 
作者:vipshop_fin_dev 
來源:CSDN 
原文:https://blog.csdn.net/vipshop_fin_dev/article/details/82819688 

2. 數據競爭

指的是併發條件下,狀態屬性信息不同步,產生讀寫誤差。

private static Parser parser;

public static Parser getInstance()
{
    if(parser == null)
        parser = new Parser();
    return parser;
}

線程1首先調用getInstance方法,檢查到 parser 屬性爲null,於是創建了一個parser對象。當線程2調用getInstance方法時,正常情況是檢查到parser不爲null,直接返回parser。另一種可能是由於狀態屬性信息不同步,檢測到仍爲null,也創建了一個parser。

3. 緩存變量

現代的處理器使用寫緩衝區臨時保存向內存寫入的數據。這個方式帶來的好處很多,可以提高運行效率,但每個處理器上的寫緩衝區,僅僅對它所在的處理器可見。即,每條線程都會有自己的變量拷貝,但其他線程不太可能知道其變量拷貝發生的改變。

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