CAS無鎖實現原理以及ABA問題


       CAS(比較與交換,Compare and swap) 是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱為“無鎖編程算法”( Non-blocking algorithm)。

為什么要用CAS

       在多線程高並發編程的時候,最關鍵的問題就是保證臨界區的對象的安全訪問。通常是用加鎖來處理,其實加鎖本質上是將並發轉變為串行來實現的,勢必會影響吞吐量。而且線程的數量是有限的,依賴於操作系統,而且線程的創建和銷毀帶來的性能損耗是不可以忽略掉的。雖然現在基本都是用線程池來盡可能的降低不斷創建線程帶來的性能損耗。對於並發控制而言,鎖是一種悲觀策略,會阻塞線程執行。而無鎖是一種樂觀策略,它會假設對資源的訪問時沒有沖突的,既然沒有沖突就不需要等待,線程不需要阻塞。那多個線程共同訪問臨界區的資源怎么辦呢,無鎖的策略采用一種比較交換技術CAS(compare and swap)來鑒別線程沖突,一旦檢測到沖突,就重試當前操作直到沒有沖突為止。

       與鎖相比,CAS會使得程序設計比較復雜,但是由於其優越的性能優勢,以及天生免疫死鎖(根本就沒有鎖,當然就不會有線程一直阻塞了),更為重要的是,使用無鎖的方式沒有所競爭帶來的開銷,也沒有線程間頻繁調度帶來的開銷大,他比基於鎖的方式有更優越的性能,所以在目前被廣泛應用,我們在程序設計時也可以適當的使用。不過由於CAS編碼確實稍微復雜,而且jdk作者本身也不希望你直接使用unsafe(后面會講到)來進行代碼的編寫,所以如果不能深刻理解CAS以及unsafe還是要慎用,使用一些別人已經實現好的無鎖類或者框架就好了。

CAS原理分析

       一個CAS方法包含三個參數CAS(V,E,N)。V表示要更新的變量,E表示預期的值,N表示新值。只有當V的值等於E時,才會將V的值修改為N。如果V的值不等於E,說明已經被其他線程修改了,當前線程可以放棄此操作,也可以再次嘗試次操作直至修改成功。基於這樣的算法,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的干擾(臨界區值的修改),並進行恰當的處理。

      額外引申技術點:上面說到當前線程可以發現其他線程對臨界區數據的修改,這點可以使用volatile進行保證。volatile實現了JMM中的可見性。使得對臨界區資源的修改可以馬上被其他線程看到,它是通過添加內存屏障實現的。具體實現原理請自行搜索volatile

ABA 問題

       由於 CAS 設計機制就是獲取某兩個時刻(初始預期值和當前內存值)變量值,並進行比較更新,所以說如果在獲取初始預期值和當前內存值這段時間間隔內,變量值由 A 變為 B 再變為 A,那么對於 CAS 來說是不可感知的,但實際上變量已經發生了變化;解決辦法是在每次獲取時加版本號,並且每次更新對版本號 +1,這樣當發生 ABA 問題時通過版本號可以得知變量被改動過。JDK 1.5 以后的 AtomicStampedReference 類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當前引用是否等於預期引用,並且當前標志是否等於預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。

循環時間長開銷大

       所謂循環時間長開銷大問題就是當 CAS 判定變量被修改了以后則放棄本次修改,但往往為了保證數據正確性該計算會以循環的方式再次發起 CAS,如果多次 CAS 判定失敗,則會產生大量的時間消耗和性能浪費;如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

只能保證一個共享變量的原子操作

  1. CAS 只對單個共享變量有效,當操作涉及跨多個共享變量時 CAS 無效;從 JDK 1.5開始提供了 AtomicReference 類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行 CAS 操作
  2. Unsafe是CAS的核心類,Java無法直接訪問底層操作系統,而是通過本地(native)方法來訪問。不過盡管如此,JVM還是開了一個后門,JDK中有一個類Unsafe,它提供了硬件級別的原子操作。
  3. valueOffset表示的是變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的原值的。
  4. value是用volatile修飾的,保證了多線程之間看到的value值是同一份。

在java領域的廣泛應用

jdk中的CAS實現java.util.concurrent.atomic包,該包下的類都是采用CAS來實現的無鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM