CAS,全稱 Compare And Swap (比較與交換),是一種樂觀鎖,一樣是鎖相比 synchronized 性能卻要高很多,由於是 synchronized 阻塞的,而CAS是非阻塞的。CAS主要有3個操做數,內存值V,預期的舊值A,須要修改的新值B,能夠這樣理解:在V的地址把A改爲B,當V位置的值與預期的舊值A相同時候,則修改爲B,不然不更新。java
下面看個圖簡單理解一下CAS:當線程1和線程2同時操做內存V,線程1想要把內存V的變量值從A(2)改爲B(1)而線程2想要把V的變量值從A(2)改爲B(3)。假設這個時候是線程1優先搶到資源因此線程1先進行CAS操做,這個時候預期舊值2是相等的則執行了更新,更新完後內存V的變量值就變成1,這個時候線程2才進入比較預期的A值與V中實際的變量值已經不相同了,因此更新失敗。ide
這個圖看上去是Compare And Swap 是同時操做,但其實是分2部執行:1.比較(compare),2.交換(swap),它的原子性是經過硬件實現的,而不是咱們java代碼實現函數
java提供的CAS操做類性能
咱們隨便找其中一個Atomic類學習學習
當V的值與A相等則更新成功測試
public static void main(String[] args) { // 定義Integer的CAS類 AtomicInteger AI = new AtomicInteger(); // 設置初始化值 AI.set(1); // 將 1 替換成 2 並返回是否替換成功,該函數第一個參數爲預期的舊值(A),第二個參數是須要修改的值(B) boolean b = AI.compareAndSet(1, 2); // 打印是否替換成功 System.out.println(b); // 打印最新值 System.out.println(AI); }
打印結果:this
當V的值與A不相等則更新失敗spa
public static void main(String[] args) { // 定義Integer的CAS類 AtomicInteger AI = new AtomicInteger(); // 設置初始化值 AI.set(1); // 將 1 替換成 2 並返回是否替換成功,該函數第一個參數爲預期的舊值(A),第二個參數是須要修改的值(B) boolean b = AI.compareAndSet(2, 2); // 打印是否替換成功 System.out.println(b); // 打印最新值 System.out.println(AI); }
打印結果:線程
這2個打印結果的結論也證明了最開始那個圖的原理,只有V的變量值與A相同時候,纔會修改爲B ,3d
接下來看看原子性
普通的int類型
public class AmIntegerTest implements Runnable { private static volatile int I = 0; public void run() { // 每一個線程自增100000次, for (int i = 0; i++ < 100000; add()) {} // 線程執行完以後結果 System.out.println(Thread.currentThread().getName() + ":" + I); } public static void add(){ I++; } public static void main(String[] args) { AmIntegerTest ait = new AmIntegerTest(); Thread t1 = new Thread(ait); Thread t2 = new Thread(ait); t1.start(); t2.start(); } }
打印結果顯示,普通的int沒有原子性
除非加上synchronized 關鍵字,接下來改造add()添加synchronized其餘代碼不變
public synchronized static void add(){ I++; }
不管執行多少次最終結果都是200000,能夠保證原子性
咱們再看看java提供的CAS操做類
public class AmIntegerTest implements Runnable { // 定義Integer的CAS類 private static AtomicInteger AI = new AtomicInteger(); public void run() { // 每一個線程自增100000次, for (int i = 0;i++ < 100000; AI.incrementAndGet()) {} // 線程執行完以後結果 System.out.println(Thread.currentThread().getName() + ":" + AI.get()); } public static void main(String[] args) { AmIntegerTest ait = new AmIntegerTest(); Thread t1 = new Thread(ait); Thread t2 = new Thread(ait); t1.start(); t2.start(); } }
不管執行多少次最終結果都是200000,因此是具備原子性的,可是它的原子性是其餘語言實現的,這裏就不討論它的實現原理了
AtomicInteger.value是一個volatile 修飾的變量(內存鎖定,同一時刻只有一個線程能夠修改內存值)
private volatile int value;
AtomicInteger.incrementAndGet()這是一個自增函數,實現了自旋鎖(無限循環),下面看看incrementAndGet()的源代碼,它調用了Unsafe 類的getAndAddInt()。而getAndAddInt()是無限循環,直到值修改爲功才結束,不然一直循環
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
private static final Unsafe unsafe = Unsafe.getUnsafe();
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; }
getAndAddInt()實現的自旋鎖原理就是內存V的變量值與A不一致時候,再從新獲取V的變量值,直到V的變量值與A一致時候,才更新成B並結束,這樣有個缺點就是若是自旋次數太多,會形成很大的資源消耗
在Atomic包中的CAS操做都是基於如下3個方法實現,Unsafe類裏面的全部方法都是 native 聲明的,說明是調用其餘語言實現的
//第一個參數o爲給定對象,offset爲對象內存的偏移量,經過這個偏移量迅速定位字段並設置或獲取該字段的值, //expected表示指望值,x表示要設置的值,下面3個方法都經過CAS原子指令執行操做。 public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
咱們試試使用一下這些函數
public class CasTest { // 定義java 的CAS類Unsafe public static Unsafe U = getUnsafe(); public static void main(String[] args) throws NoSuchFieldException { // java Unsafe類調用C++ 實現的CAS // 建立一個咱們的測試對象 Cas cas = new Cas("1"); // 獲取對象內value存儲對象的地址 long offset = U.objectFieldOffset(cas.getClass().getDeclaredField("value")); // 經過Unsafe類的CAS函數進行修改 System.out.println(U.compareAndSwapObject(cas, offset, "1", "2")); System.out.println(cas); } static class Cas{ private String value; public Cas(String value) { this.value = value; } @Override public String toString() { return "Cas{" + "value='" + value + '\'' + '}'; } } // 由於 Unsafe的構造函數是私有的,並且它提供的 getUnsafe()也只有系統類才能使用, // 因此咱們只能經過反射獲取Unsafe實例了 private static Unsafe getUnsafe() { Unsafe unsafe = null; try { Constructor<?> declaredConstructor = Unsafe.class.getDeclaredConstructors()[0]; declaredConstructor.setAccessible(true); unsafe = (Unsafe) declaredConstructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } return unsafe; } }
看一下main()的打印結果,修改cas對象的value值,從「1」改爲「2」,執行成功
改一下main(),試一下從「2」改爲「2」
public static void main(String[] args) throws NoSuchFieldException { // java Unsafe類調用C++ 實現的CAS // 建立一個咱們的測試對象 Cas cas = new Cas("1"); // 獲取對象內value存儲對象的地址 long offset = U.objectFieldOffset(cas.getClass().getDeclaredField("value")); // 經過Unsafe類的CAS函數進行修改 System.out.println(U.compareAndSwapObject(cas, offset, "2", "2")); System.out.println(cas); }
這個時候修改是失敗的,由於value的值爲「1」,而CAS操做裏 預期的舊值是「2」,因此沒法成功執行從「2」改爲「2」
下面2個函數都是Unsafe類裏面的,都是私有的,並且它提供的 getUnsafe()也不能使用,因此須要經過反射獲取實例
private Unsafe() { } @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
ABA問題
ABA就是內存V的變量值從A變成B,再從B變成A,這個時候CAS只判斷值是否相等,只要值相等就會認爲這個值沒改變過,但其實是已經變化了。咱們能夠添加多一個標識(版本號、時間)判斷這個值是否已經改變過,java也提供了相關的解決方案 AtomicStampedReference 類。
先看一下普通CAS的操做類
public static void main(String[] args) { // 定義Integer的CAS類 AtomicInteger AI = new AtomicInteger(1); System.out.println("初始化值:" + AI); AI.compareAndSet(1,2); System.out.println("第一次CAS操做完成以後值:" + AI); AI.compareAndSet(2,1); System.out.println("第二次CAS操做完成以後值:" + AI); AI.compareAndSet(1,3); System.out.println("第二次CAS操做完成以後值:" + AI); }
這種只須要V的值與A一致就能夠修改,存在ABA問題
接下來看看CAS的標識引用類
public static void main(String[] args) { // 定義 AtomicStampedReference 類,第一個參數是咱們的初始化值,第二個是版本標識 AtomicStampedReference<Integer> ASR = new AtomicStampedReference<Integer>(1,1); System.out.println("初始化值:" + ASR.getReference()); ASR.compareAndSet(1,2, ASR.getStamp(), ASR.getStamp() + 1); System.out.println("第一次CAS操做完成以後值:" + ASR.getReference()); // 獲取stamp ,下面2次新增都使用這個 stamp值 int stamp = ASR.getStamp(); // 這一次能夠修改爲功,由於 stamp 的預期值一致 ASR.compareAndSet(2,1, stamp, stamp + 1); System.out.println("第二次CAS操做完成以後值:" + ASR.getReference()); // 這一次能夠修改失敗,由於 stamp 的預期值不一致 ASR.compareAndSet(1,3, stamp, stamp + 1); System.out.println("第三次CAS操做完成以後值:" + ASR.getReference()); }
這種不緊V的值要與A相等,且記錄的標識也須要一致才能完成修改
CAS是樂觀鎖,synchronized是悲觀鎖。二者各有優異,應對不一樣場景選擇合適的鎖。