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