2012-07-05 23 views
0

我在線程上遇到了這個小問題。簡單多線程代碼有待澄清

int x = 0; 
    add() { 

     x=x+1; 
    } 

如果我們在多個線程中運行這個,說4個線程,是X = 4的最終值在每一次,也可能是1,2,3或4

感謝

PS 可以說爲加入原子操作是這樣的,

LOAD A x 
ADD A 1 
LOAD x A 

那麼最終的結果將是4,我說得對不對或者我有什麼拿錯?

+0

我建議你把一個語言標籤獲得更多的關注,儘管它看起來像一個普遍的問題。 – Tudor 2012-07-05 13:05:56

+0

你是在說4個'threads'還是4個'for-loop'? – Turbot 2012-07-05 13:07:59

回答

3

這是一個經典的數據競賽的例子。

現在,讓我們在什麼加細看()的作用:

add() 
{ 
    x = x + 1; 
} 

這相當於:

  1. 給我最近的X的值,並將其存放在我的私人工作空間
  2. 將1添加到存儲在我的私人工作區中的值
  3. 將我在工作區中的內容複製到我從中複製的內存(即全局訪問)。

現在,我們在解釋這個進一步走之前,你有一些所謂context switch,這是由你的操作系統將您的處理器的不同線程和進程之間的時間過程。這個過程通常會給你的線程有限的處理器時間(在windows上大約40毫秒),然後中斷這個工作,將處理器在寄存器中的所有內容都複製下來(並且保存它的狀態)並切換到下一個任務。這被稱爲Round-robin task scheduling

您無法控制何時處理將被中斷並轉移到另一個線程。

現在,假設你有兩個線程做同樣的:

1. Give me the most recent value of X and store it in my private workspace 
2. Add 1 to that value that is stored in my private workspace 
3. Copy what I have in my workspace to the memory that I copied from (that is globally accessible). 

和X等於1它們中的任何運行之前。

第一個線程可能執行第一條指令,並在它的私有工作空間中存儲X在工作時最近的值 - 1.然後發生上下文切換,操作系統中斷您的線程並控制隊列中的下一個任務,這恰好是第二個線程。第二個線程也讀取等於1的X的值。

線程號碼2管理完成運行 - 它將「下載」和「上載」計算值的值加1。

操作系統再次強制上下文切換。

現在第一個線程在被中斷的地方繼續執行。它仍會認爲最近的值是1,它會將該值增加1並將其計算結果保存到該內存區域。這就是數據競賽的發生。您預計最終結果爲3,但爲2.

有很多方法可以避免此問題,如locks/mutexes,compare and swapatomic operations

+1

+1。很徹底。做得好。 – Tudor 2012-07-05 13:18:53

+0

我只會評論這討論實施細節。但是,最好將注意力集中在Java語言規範提供的保證(特別是**,不提供**)上。您可以在完全不同的體系結構上運行Java,其中大多數這些點都將失效。 – 2012-07-05 13:28:14

2

你的代碼是在兩個層面上打破:

  1. 沒有happens-before線程的動作之間施加關係;
  2. 獲取與增量的原子性未強制執行。

要解決1.您可以添加volatile修飾符。這仍然會使操作不是原子的。爲確保原子性,您最好使用AtomicInteger或​​(涉及鎖定,不是首選)。

就目前而言,如果從不參與遞增的線程讀取結果,則結果可以是從0到4的任意數字

2

多線程應用程序是併發的(這是整點)。

t1: LOAD A1 x 
t2: LOAD A2 x 
t3: LOAD A3 x 
t4: LOAD A4 x 
t1: ADD A1 1 
t2: ADD A2 1 
t3: ADD A3 1 
t4: ADD A4 1 
t1: STORE x A1 
t2: STORE x A2 
t3: STORE x A3 
t4: STORE x A4 

A1,A2,A3,A4是本地寄存器。

結果是1,但它可能是2,34。如果你有另一個線程,它可以看到由於可見性問題的舊值,並參見0