1. 程式人生 > >JAVA中對CAS演算法的應用

JAVA中對CAS演算法的應用

CAS演算法:Compare and Swap比較並交換。總共由三個運算元,一個記憶體值v,一個執行緒本地記憶體舊值a(期望操作前的值)和一個新值b,在操作期間先拿舊值a和記憶體值v比較有沒有發生變化,如果沒有發生變化,才能記憶體值v更新成新值b,發生了變化則不交換。迴圈CAS演算法則是不停的執行CAS操作。java.util.concurrent.atomic包下的原子變數型別,比如AtomicInteger,都使用了這些底層的JVM支援為數字型別的引用型別提供一種高效的CAS操作。

java中synchronized關鍵字在jdk1.5版本之後,也做出了優化,在這之前synchronized一直是重量級的鎖,不管什麼情況上來就直接給物件加上互斥鎖,導致在某些情況下效率低下。1.5版本之後,對synchronized採用了鎖升級的策略,

偏向鎖→輕量級鎖→自旋鎖→重量級鎖。 其中輕量級鎖採用的就是類似於cas演算法來實現的,

關於java中的輕量級鎖:執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,則自旋獲取鎖,當自旋獲取鎖仍然失敗時,表示存在其他執行緒競爭鎖(兩條或兩條以上的執行緒競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。 CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題:ABA問題、迴圈時間長開銷大、只能保證一個共享變數的原子操作。

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

 如果當前引用 == 預期引用,並且當前標誌等於預期標誌,則以原子方式將該引用和該標誌的值設定為給定的更新值。其中引數代表:expectedReference - 該引用的預期值;newReference - 該引用的新值;

expectedStamp - 該標誌的預期值;newStamp - 該標誌的新值。

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

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

        原子類不是 java.lang.Integer 和相關類的通用替換方法。它們不 定義諸如 hashCode 和 compareTo 之類的方法。(因為原子變數是可變的,所以對於雜湊表鍵來說,它們不是好的選擇。)另外,僅為那些通常在預期應用程式中使用的型別提供類。例如,沒有表示 byte 的原子類。這種情況不常見,如果要這樣做,可以使用 AtomicInteger 來保持 byte 值,並進行適當的強制轉換。也可以使用 Float.floatToIntBits 和 Float.intBitstoFloat 轉換來保持 float 值,使用 Double.doubleToLongBits 和 Double.longBitsToDouble 轉換來保持 double 值。  

        類 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的例項各自提供對相應型別單個變數的訪問和更新。每個類也為該型別提供適當的實用工具方法。例如,類 AtomicLong 和 AtomicInteger 提供了原子增量方法。一個應用程式將按以下方式生成序列號: