1. 程式人生 > >執行緒三:多執行緒的問題

執行緒三:多執行緒的問題

1、競態條件

        當計算結果的正確性取決於相對時間或者排程器控制的多執行緒交叉時,就會發生競態條件。這句話可能對初次接觸執行緒的讀者來說不太好理解,其實競態條件有兩個相對比較好理解的描述,一個是check-then-act,另外一個是read-modify-write。

        check-then-act通常指的是用"過時"的狀態去決定下一步的動作。比如看一個例子:if(number == 100) mynumber = number * 10;假設其中一個執行緒ThreadA在執行完number == 100的判斷為真後,馬上要執行mynumber = number * 10的時候,ThreadA被排程器暫停了,此時執行緒ThreadB執行了number = 20的操作,當ThreadA再次恢復執行的時候,mynumber的結果已經不是1000了。這個結果的前提是number和mynumber是成員屬性而不是區域性屬性,因為每個執行緒都有自己的區域性屬性拷貝,不會出現競態條件。

        read-modify-write問題通常指的是多個執行緒同步執行導致讀、寫、改同一個資料時並沒有按照一個原子動作去執行。比如看一個例子:public int getNumber( ){ return number++; }這個方法看起來是一個動作,其實number++是三個獨立的操作:讀取number的值,為number加1,最後把更新之後的值賦給number。假設執行緒ThreadA呼叫了getNumber方法並在讀取了number的值後,執行緒ThreadB開始運行了,同樣呼叫了getNumber方法並讀取了number的值,在為number加1之後返回。此時執行緒ThreadA恢復了執行,同樣在原來讀取的number數值之上加了1,返回到呼叫處。此時執行緒ThreadA在結果上實際撤銷了執行緒ThreadB的操作,系統錯過了一次加1的操作。

2、資料競爭

       資料競爭指的是在同一個應用中多個執行緒併發訪問同一塊記憶體區,其中至少有一個執行緒是進行寫操作。而且這些執行緒沒有對這塊記憶體區的訪問進行協調,導致每次執行都會產生不一樣的執行結果。為了描述資料競爭,我們看一個例子:

       假設執行緒ThreadA呼叫了getMyIns方法,由於my物件是空值,ThreadA會為其建立例項並賦值給my變數。此時執行緒ThreadB呼叫getMyIns方法,它可能會檢測到非空值my並返回,也可能會檢測到my為空值,於是也建立了一個新的My類物件。由於執行緒ThreadA和執行緒ThreadB之間沒有happens-before ordering(動作先後)的保證,這時就發生了資料競爭。

3、快取變數

快取變數是由於系統要提升效能帶來的問題,通常JVM會為每個執行緒提供一個自己的快取區用來存放自己的變數拷貝,而不是依賴於系統主存。當執行緒對這個快取區進行變數操作時,其他執行緒是不可見的。為了描述快取變數,我們看一個例項:

       例子中的類屬性my演示了快取變數的問題。執行緒thread完成對my屬性的操作,然後主執行緒執行輸出語句,輸出結果。問題是,執行緒thread能夠將返回值儲存到自己的my變數的拷貝中,主執行緒很可能無法看到賦值的結果。主執行緒很可能將本地的null結果打印出來。