1. 程式人生 > >CAS實現原子操作的三大問題

CAS實現原子操作的三大問題

在Java併發包中有一些併發框架也使用了自旋CAS的方式來實現原子性,比如LinkTransferQueue類的Xfer方法。CAS雖然很高效 的解決了原子操作,但是CAS仍然存在三大問題:ABA問題,迴圈時間開銷大,以及只能保證一個共享變數的原子操作。

(1)ABA問題

因為CAS需要在操作值的時候,檢查值有麼有發生變化,如沒有發生變化則更新,但是如果原來的一個值是A,變成了B,又變成了A,那麼使用CAS進行檢查的時候,會發現他的值沒有發生變化,但是實際上卻發生了變化。ABA問題的解決思路就是使用版本號。在變數前面追加版本號,每次變數更新的時候把版本號加1,那麼A->B->A就變成了1A->2B->3A。在Java1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部等於,則以原子方式將該引用和該標誌的值設定為給定的更新值。

public boolean compareAndSet(
    v expectedReference, //預期引用
    V newReference,      //更新後的引用
  int expectedStamp,     //預期標誌
  int newStamp           //更新後的標誌
)

(2) 迴圈時間開銷大

自旋CAS如果長時間不動,會給CPU帶來非常大的執行開銷,如果JVM能支援處理器提供的pause指令,那麼效率會有一定的提升。pause指令有兩個作用:第一,它可以延遲流水線執行指令,是CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零;第二,它可以避免在退出迴圈的時候因記憶體順序衝突而引起的CPU流水線被清空,從而提高記憶體CPU的執行效率。

(3)只能保證一個共享變數的原子性

當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的方法,就是把多個共享變數合併成一個共享變數來操作,比如,有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Jaca1.5開始,JDK提供了AtomicReference類來保證引用物件之間的原子性,就可以把多個變數放在一個物件裡來進行CAS操作。