java多執行緒環境下對變數的讀寫操作的原子性問題
阿新 • • 發佈:2019-01-10
本文轉載自: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了。