1. 程式人生 > >Java執行緒中的問題——競態條件、資料競爭

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. 快取變數

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