1. 程式人生 > >java多執行緒環境下對變數的讀寫操作的原子性問題

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了。