java多線程環境下對變量的讀寫操作的原子性問題

本文轉載自:http://www.cnblogs.com/qlee/archive/2011/09/13/2174434.html


以下多線程對int型變量x的操作,哪幾個需要進行同步:( )
A. x=y; B. x++; C. ++x; D. x=1;

從表面看上去實在是看不出什麼突破口,我們不妨將這些代碼譯成彙編語言再來分析。

 

01  x = y; 

02  mov eax,dword ptr [y] 

03  mov dword ptr [x],eax 

04   

05  x++; 

06  mov eax,dword ptr [x] 

07  add eax,1 

08  mov dword ptr [x],eax 

09   

10  ++x; 

11  mov eax,dword ptr [x] 

12  add eax,1 

13  mov dword ptr [x],eax 

14   

15  x = 1; 

16  mov dword ptr [x],1 


 

(1)很顯然,x=1是原子操作。

因爲x是int類型,32位CPU上int佔32位,在X86上由硬件直接提供了原子性支持。實際上不管有多少個線程同時執行類似x=1這樣的賦值語句,x的值最終還是被賦的值(而不會出現例如某個線程只更新了x的低16位然後被阻塞,另一個線程緊接着又更新了x的低24位然後又被阻塞,從而出現x的值被損壞了的情況)。

 

(2)再來看x++和++x。
其實類似x++, x+=2, ++x這樣的操作在多線程環境下是需要同步的。因爲X86會按三條指令的形式來處理這種語句:從內存中讀x的值到寄存器中,對寄存器加1,再把新值寫回x 所處的內存地址(見上面的反彙編代碼)。

例如有兩個線程,它們按照如下順序執行(注意讀x和寫回x是原子操作,兩個線程不能同時執行):

time    Thread 1         Thread 2
0      load eax, x
1                            load eax x
2      add eax, 1        add eax, 1
3      store x, eax
4                            store x, eax

 

我們會發現最終x的值會是1而不是2,因爲Thread 1的結果被覆蓋掉了。這樣情況下我們就需要對x++這樣的操作加鎖(例如Pthread中的mutex)來保證同步,或者使用一些提供了atomic operations的庫(例如Windows API中的atomic 庫 ,Linux內核中的atomic.h ,Java concurrent庫中的Atomic Integer,C++0x中即將支持的atomic_int等等,這些庫會利用CPU提供的硬件機制做一層封裝,提供一些保證了原子性的API)。

(3)最後來看看x=y。
在X86上它包含兩個操作:讀取y至寄存器,再把該值寫入x。讀y的值這個操作本身是原子的,把值寫入x也是原子的,但是兩者合起來是不是原子操作呢?我個人認爲x=y不是原子操作,因爲它不是不可再分的操作。但是它需要不需要同步呢?其實問題的關鍵在於程序的上下文。

例如有兩個線程,線程1要執行{y = 1; x = y;},線程2要執行{y = 2; y = 3;},假設它們按如下時間順序執行:

time    Thread 1        Thread 2
0        store y, 1
1                            store y, 2
2        load eax, y
3                            store y, 3
4        store x, eax

 

那麼最終線程1中x的值爲2,而不是它原本想要的1。我們需要加上相應的同步語句確保y = 2不會在線程1的兩條語句之間發生。y = 3那條語句儘管在load y和store x之間執行,但是卻不影響x=y這條語句本身的語義。所以你可以說x=y需要同步,也可以說x=y不需要同步,看你怎麼理解題意了。x=1是否需要同步也是一樣的道理,雖然它本身是原子操作,但是如果有另一個線程要讀x=1之後的值,那肯定也需要同步,否則另一個線程讀到的就是x的舊值而不是1了。

發佈了34 篇原創文章 · 獲贊 45 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章