CAS (Compare And Swap)
比較並交換html
CAS 是一條CPU併發原語。功能是判斷內存某個位置的值是否爲預期值,若是是則更改成最新值,這個過程是原子的。CAS併發原語體如今Java語言中就是sun.misc.Unsafe類中的各個本地方法。這是一種徹底依賴於硬件
的功能,經過它實現了原子操做。原語的執行時連續的,在執行過程當中不容許被中斷,也就是說CAS是一條CPU的原子指令,不會形成數據不一致。java
CAS的做用是比較當前工做內存中的值和主內存中的值,若是相同則執行規定操做,不然繼續比較直到主內存和工做內存中的值一致爲止。 若是單看這句話不知道在說什麼,那麼就繼續往下看,通篇結束以後再看讀一下這句話吧。安全
這一篇經過AtomicInteger中兩個方法來探索CAS究竟是如何實現的。多線程
先來看三行代碼併發
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,10)+ "\t current data : " +atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,9)+ "\t current data : " +atomicInteger.get());
複製代碼
上篇文章對volatile的理解 提到了,可使用原子類AtomicInteger 解決原子性問題。這裏用到了AtomicInteger.compareAndSet方法,兩個參數分別是指望值
和更新值
,若是指望值
與此時內存中的值相同,則將內存中的值更新爲更新值
,方法返回值爲布爾類型,表示是否更新成功。以上三行代碼,初始化時設置內存中值爲5,第二行指望值與更新值分別爲5和10,若是指望值5與內存中的值5相同,則將內存中的值更新爲10,第二行返回true,當前內存中的值爲10;第三行指望值爲5,當前內存中的值爲10,不符合指望結果,則不更新內存中的結果,返回false,當前內存中的值未更改,仍是10。代碼輸出結果以下:post
true current data : 10
false current data : 10
複製代碼
概述一下,CAS涉及到3個操做數,內存值V,舊的預期值A,要修改的更新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。this
咱們知道在多線程狀況下i++
操做其實包含了三個步驟,是線程不安全的。咱們可使用atomicInteger.getAndIncrement()
實現線程安全的i++
,下面經過看下該方法的實現瞭解如何作到線程安全。atom
/** * Atomically increments by one the current value. * * @return the previous value */
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
複製代碼
以上是getAndIncrement的源代碼,註釋在當前值的基礎上原子增長一。實現爲調用unsafe
類的getAndAddInt
方法,該方法有三個參數,分別是當前對象、內存偏移量、1,再往下看,就進入到unsafe類。spa
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
複製代碼
瞭解這幾個概念,咱們繼續往下看,getAndAddInt
是如何實現的:操作系統
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
複製代碼
這仍然是存在於unfase的方法,var1對應this,即當前對象,var2爲內存偏移量,var4便是要添加的「1」。
當前方法爲do-while結構,根據方法名中getAndAdd
,首先get
操做,do結構首先根據對象與內存地址獲取當前值var5,從物理主內存中拷貝到本身的線程工做內存;而後關鍵點來了:while裏面判斷若是這一時刻內存地址中的值與var5相同,則修改成var5+var4,即Add
成功,這時返回true,while取反,條件不成立,再也不執行do;若是剛拿到var5的值,主物理內存中的值就發生變化了,即主內存與var5的值不一致,則不修改,返回false,while條件知足,繼續執行do操做,再次獲取當前內存中最新的值,繼續判斷,直至保證加一時拿到的結果時最新的。
以上操做簡單說,就是拿到var5,而後對var5+1,擔憂加一操做時var5的值已經發生變化,因此加一時要再驗證一遍,若是這個內存中的值仍是var5那麼就放心的加一操做,不然就不停的去獲取最新的值。
下面舉個例子來具體解釋上一段內容:
假設線程A和線程B兩個線程同時執行getAndAddInt操做:
getIntVolatile(var1,var2)
拿到value值爲3,這是A被掛起。getIntVolatile(var1,var2)
拿到value值爲3,此時恰好相線程B沒有被掛起並執行compareAndSwapInt
方法比較內存值也是3,成功修改內存值爲4,線程B完成操做。compareAndSwapInt
方法,發現本身工做內存中的數字3與主內存中的數字4不一致,說明該值已經被其餘線程搶先修改過了,那麼A線程本次修改失敗,從新讀取最新值重來一遍
,即從新執行do操做。compareAndSwapInt
進行比較替換,直至成功。再概述一下,CAS有3個操做數,內存值V,舊的預期值A,要修改的更新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。 這時再去看開篇的那句話是否就理解了呢。
getAndAddInt
方法中的第一個參數是object,即this 表示當前對象,從數量上來講這裏只能控制一個共享變量。而synchronized加鎖能鎖住一段代碼,能夠保證多個線程都被鎖住。儘管線程A的CAS操做成功,可是不表明這個過程就沒有問題。