CAS機制總結


一、簡介

CAS機制:(Compare and set)比較和替換

  簡單來說–>使用一個期望值來和當前變量的值進行比較,如果當前的變量值與我們期望的值相等,就用一個新的值來更新當前變量的值
CAS有三個操作數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時(條件),將內存值修改為B並返回true,否則條件不符合返回false。條件不符合說明該變量已經被其它線程更新了。

  當多個線程訪問相同的數據時,如果使用鎖來進行並發控制,當某一個線程(T1)搶占到鎖之后,那么其他線程再嘗試去搶占鎖時就會被阻塞,當T1釋放鎖之后,下一個線程(T2)再搶占到鎖后並且重新恢復到原來的狀態線程從阻塞到重新准備運行有很長的時間開銷。而假設我們業務代碼本身並不具備很復雜的操作,並發量也不高,那么當我們使用CAS機制來代替加鎖操作,當多個線程操作同一變量時每個線程會首先會讀取到地址值對應的原值作為自己的期望值,然后進行操作,操作完以后在更新的時候他會判斷現在地址值對應的值是否與自己的期望值相同,如果相同,就認為自己操作的過程中別人沒有進行過操作。則將自己操作后的值更新到地址值對應的位置,如果不同則說明自己操作的過程中一定有其他的線程更新過數據,然后會把當前地址值對應的值返回給用戶,用戶拿到新值重新上次的操作。不斷循環直到更新成功。

二、用途

CAS的使用場景:juc下ReentryLock 和 Atomic類操作

CAS樂觀鎖(循環內自旋):原理:A=內存值,thread1對A進行累加操作后的值為B。更新內存值時會判斷A和B是否相等,如果相等,那么B替換A退出循環,如果不相等,重新獲得內存值,進行操作。此套流程如何保證內存值是最新的?詳見volatile原理此套流程如何保證V,A比較B替換V時是原子操作?cas底層用unsafe直接訪問底層操作系統,做了硬件級別的原子操作。

AtomicInteger源碼分析
java.util.concurrent.atomic包下的原子操作類都是基於CAS實現的,接下去我們通過AtomicInteger來看看是如何通過CAS實現原子操作的:

public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates //獲得unsafe實例
    private static final Unsafe unsafe = Unsafe.getUnsafe(); //存放變量value的內存偏移
    private static final long valueOffset; static { try { //通過unsafe獲得value的內存偏移
            valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //volatile修飾的,保證了多線程之間看到的value值是同一份,后面會分析
    private volatile int value;

接下來看看設置新的值是如何完成的:

public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

這里很簡單,直接調用unsafe的cas方法就可以了,通過value的內存偏移,以及期望的值來設置新的值。接下來就是本地方法調用了。

 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }

同樣原子增加的操作就是通過unsafe來完成,只是每次都是增加1而已。
可以去看看其他幾個原子操作類:AtomicLong(原子更新長整型)AtomicBoolean(原子更新布爾類型,用int來實現的),AtomicIntegerArray(原子更新整型數組里的元素),AtomicReference(原子更新引用類型)等,其核心思想都是用unsafe提供的原子操作來完成的。
在Java並發編程的藝術中,作者實現了一個基於CAS線程安全的計數器和一個非線程安全的計數器,本質就是用原子操作類代替一般的int或者Long數據類型,通過原子操作類來完成原子操作,保證了計數的線程安全,但是這里提一下,原子操作類不是什么時候都是線程安全的,當由原子操作類來完成復合操作是,此時就不一定是線程安全的了。

 AtomicInteger a=new AtomicInteger(10); //假設下面會出現線程競爭
        int b=a.get(); if (b==10){ a.compareAndSet(10,100); }

在多線程中,這樣就不是線程安全的了,因為先取出某個值,然后在判斷,這整個操作不是原子操作。


 

三、優缺點

cas優點:

  如一描述在並發量不是很高時cas機制會提高效率。

cas缺點:

1.循環時間開銷太大:

  如果CAS長時間執行不成功,則會給CPU帶來交大的執行開銷。處理器提供一種pause指令可以緩解這部分問題,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

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

  如果需要對多個共享變量進行同步,就得使用鎖,或者將幾個共享變量封裝起來,使用CAS來進行同步。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作

3、ABA問題

aba問題:內存值V=100;
threadA 將100,改為50;
threadB 將100,改為50;
threadC 將50,改為100;

場景:小牛取款,由於機器不太好使,多點了幾次全款操作。后台threadA和threadB工作,
此時threadA操作成功(100->50),threadB阻塞。正好牛媽打款50元給小牛(50->100),
threadC執行成功,之后threadB運行了,又改為(100->50)。
牛氣沖天,lz錢哪去了???

如何解決aba問題:對內存中的值加個版本號,在比較的時候除了比較值還的比較版本號。

java:AtomicStampedReference就是用版本號實現cas機制。


免責聲明!

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



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