什麼是CAS
CompareAndSet 比較交換 :比較目前記憶體和主記憶體中的值,如果達到預期,就進行交換,如果沒有達到,就一直循環。
缺點:1.由于底層是自旋鎖,循環較為耗時 2.一次隻能保證一個共享變量的原子性 3.存在ABA問題
在Java的原子類中存在CAS方法,其方法底層則是調用Unsafe類中的CAS方法。

什麼是Unsafe類:它是Java用來修改記憶體中變量的一個後門類,其底層都是C++對于記憶體變量的直接修改方法。
以AtomicInteger的加法為例,可以看到它的底層就是使用的自旋鎖的CAS方法,我們來解釋一下這個CAS方法的原理
var1是我們傳入進來對象的位址,var2則是指向目前對象位址的值,當我們的var2的值達到var5這個期望值後,我們則會對于var2進行加var4計算 也就是var2的值加1
如果var2沒有達到var5期望值時,則會在自旋鎖中不斷循環。這裡就時直接對于記憶體變量的計算,效率很高。
CAS的ABA問題 ABA(狸貓換太子)
當我們兩個不同線程a,b來對于一個共享變量A進行CAS操作時,可能存在這樣的情況
我們的a線程在擷取A準備計算時,線程b要快一步,對A進行了1置換為3,然後又3置換為1 再傳回給了主記憶體
那這個時候,我們a線程拿到的A變量已經不是原有的變量A了,這就是ABA問題。
package com.gupao.juc.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSet:比較并交換
public static void main(String args[]){
AtomicInteger atomicInteger = new AtomicInteger(200);
//對于我們平時寫的SQL:樂觀鎖
//期望,更新
//public final boolean compareAndSet(int expect,int update)
//如果我期望的值達到了,那麼就更新,否則就不更新 CAS 是CPU的并發原語
//=============搗亂的線程================//
System.out.println(atomicInteger.compareAndSet(200, 201));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(201, 200));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(200, 666));
System.out.println(atomicInteger.get());
}
}
那麼我們要如何來解決ABA問題呢
原子引用
我們需要使用原子引用(相當于樂觀鎖,對于我們的CAS操作加上版本号來進行判斷)
需要用到類:AtomicStampReference
package com.gupao.juc.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo2 {
public static void main(String[] args) {
//建構AtomicStampedReference對象,版本号為1
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"版本号===>"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(1,
2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//進行一次CAS操作,版本号為2
//運作完後,列印版本号
System.out.println(Thread.currentThread().getName()+"版本号====>"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(2,
1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//再進行一次CAS操作,版本号為3
//運作完後,列印版本号
System.out.println(Thread.currentThread().getName()+"版本号====>"+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(1, 5,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//另一個線程也做一次CAS操作
System.out.println(Thread.currentThread().getName()+"版本号===>"+atomicStampedReference.getStamp());
},"b").start();
}
}
我們此時可以看到,對于每一次的CAS操作,我們都有一次版本号的對應,當我們每成功操作一次CAS後,版本号會+1,否則不會。