Java併發中原子操作的實現
處理器實現原子操作
如果多個處理器同時對共享變數進行讀改寫(i++就是經典的讀改寫操作)操作,那麼共享變數就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的,所以要保證操作是原子性,則必須保證同一時刻只有一個處理器訪問共享變數的記憶體地址。
使用匯流排鎖定保證原子性。
處理器在總線上輸出一個LOCK#訊號,那麼其他處理器的請求將被阻塞,此時該處理器就可以獨佔共享記憶體,從而保證只有一個處理器操作共享記憶體。
- 使用快取鎖定保證原子性。
匯流排鎖把CPU和記憶體之間通訊鎖住了,這使得鎖定期間,其他處理器不能操作其他記憶體地址的資料,所以匯流排鎖定的開銷比較大,現在多用快取鎖來代替匯流排鎖優化。如果快取在處理器快取行中的記憶體區域在LOCK操作期間被鎖定,當它執行鎖操作回寫記憶體時,處理器不在總線上聲言LOCK#訊號,而是修改內部的記憶體地址
JAVA如何實現原子操作
1.使用迴圈CAS實現原子操作
/**
* 使用CAS實現執行緒安全計數器
*/
private void safeCount() {
for (;;) {
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
/**
* 非執行緒安全計數器
*/
private void count() {
i++;
}
可能會遇到的問題:
ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。
迴圈時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
只能保證一個共享變數的原子操作。當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用物件之間的原子性,你可以把多個變數放在一個物件裡來進行CAS操作。
2.使用鎖機制實現原子操作
鎖機制保證了只有獲得鎖的執行緒能夠操作鎖定的記憶體區域。JVM內部實現了很多種鎖機制,有偏向鎖,輕量級鎖和互斥鎖,除了偏向鎖,JVM實現鎖的方式都用到的迴圈CAS,當一個執行緒想進入同步塊的時候使用迴圈CAS的方式來獲取鎖,當它退出同步塊的時候使用迴圈CAS釋放鎖。