淺析CAS與AtomicInteger原子類

一:CAS簡介

CAS:Compare And Swap(字面意思是比較與交換),JUC包中大量使用到了CAS,好比咱們的atomic包下的原子類就是基於CAS來實現。區別於悲觀鎖synchronized,CAS是樂觀鎖的一種實現,在某些場合使用它能夠提升咱們的併發性能。java

在CAS中,主要是涉及到三個操做數,所期盼的舊值、當前工做內存中的值、要更新的值,僅當所期盼的舊值等於當前值時,纔會去更新新值。編程

二:CAS舉例

好比當以下場景,因爲i++是個複合操做,讀取、自增、賦值三步操做,所以在多線程條件下咱們須要保證i++操做的安全安全

public class CASTest { int i = 0; public void increment() { i++; } }

解決辦法有經過使用synchronized來解決,synchronized解決了併發編程的原子性,可見性,有序性。多線程

public class CASTest { int i = 0; public synchronized  void increment() { i++; } }

但synchronized畢竟是悲觀鎖,儘管它後續進行了若干優化,引入了鎖的膨脹升級措施,可是仍是存在膨脹爲重量級鎖而致使阻塞問題,所以,咱們能夠使用基於CAS實現的原子類AtomicInteger來保證其原子性併發

public class CASTest { AtomicInteger i = new AtomicInteger(0); public  static void increment() { //自增並返回新值
 i.incrementAndGet(); } }

三:CAS原理分析

atomic包下的原子類就是基於CAS實現的,咱們拿AtomicInteger來分析下CAS.ide

public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // CAS操做是基於一個Unsafe類,Unsafe類是整個Concurrent包的基礎,裏面全部的函數都是native的
    private static final Unsafe unsafe = Unsafe.getUnsafe(); //內存偏移量
    private static final long valueOffset; static { try { //初始化地址偏移量
            valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //底層採用volatile修飾值,保證其可見性和有序性
    private volatile int value;

從AtomicInteger定義的相關屬性來看,其內部的操做都是基於Unsafe類,由於在Java中,咱們並不能直接操做內存,可是Java仍是開放了一個Unsafe類來給咱們進行操做,顧名思義,Unsafe,是不安全的,所以要謹慎使用。函數

其內部定義的值是用volatiel進行修飾的,volatile能夠保證有序性和可見性,具體爲何能夠保證就不在此闡述了。源碼分析

再來看看其幾個核心的APIpost

//以原子方式將值設置爲給定的新值 expect:指望值 update:舊值
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //以原子方式將當前值+1,返回指望值
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } //以原子方式將當前值-1,返回指望值 
public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; }

關於其源碼仍是不多的,基本都是基於Unsafe類進行實現的。性能

先來看看compareAndSet方法,其調用的是Unsafe的compareAndSwapInt方法,當工做內存中的值與所期盼的舊值不相同的時候,會更新失敗,舉例說明:

public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println("更新結果:"+atomicInteger.compareAndSet(2020, 2021)); System.out.println("當前值爲:"+atomicInteger.get()); //自增長一
 atomicInteger.getAndIncrement(); System.out.println("更新結果:"+atomicInteger.compareAndSet(2020, 2021)); System.out.println("當前值爲:"+atomicInteger.get()); } }

 

 在來看看incrementAndGet方法,其調用的是unsafe.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; }

四:CAS缺點分析

CAS的優勢很明顯,基於樂觀鎖的思想,提升了併發狀況下的性能,缺點主要是ABA問題、自旋時間過長致使CPU佔有率太高、只能保證一個共享變量的原子性。

ABA問題

就是一個值由A變爲B,在由B變爲A,使用CAS操做沒法感知到該種狀況下出現的變化,帶來的後果很嚴重,好比銀行內部員工,從系統挪走一百萬,以後還了回來,系統感知不到豈不是要出事。模擬下出現ABA問題:
public class ABA { private static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args) { //線程t1實現0->1->0
           Thread t1 = new Thread(new Runnable() { @Override public void run() { atomicInteger.compareAndSet(0,1); atomicInteger.compareAndSet(1,0); } },"t1"); //線程t2實現0->100
           Thread t2 = new Thread(new Runnable() { @Override public void run() { try { //模擬狸貓換太子行爲
                       TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("更新結果:"+atomicInteger.compareAndSet(0, 100)); } }); t1.start(); t2.start(); } } 

運行結果是:true

解決ABA能夠使每一次修改都帶上時間戳,以記錄版本號的形式來使的CAS感知到這種狸貓換太子的操做。Java提供了AtomicStampedReference類來解決,該類除了指定舊值與期盼值,還要指定舊的版本號與期盼的版本號

public boolean compareAndSet(V   expectedReference, V   newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp &&  ((newReference == current.reference && newStamp ==current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
public class ABA_Test { // 初始值100,版本號1
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) throws InterruptedException { // AtomicStampedReference實現
        Thread tsf1 = new Thread(new Runnable() { @Override public void run() { try { // 讓 tsf2先獲取stamp,致使預期時間戳不一致
                    TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } // 預期引用:100,更新後的引用:110,預期標識getStamp() 更新後的標識getStamp() + 1
                atomicStampedReference.compareAndSet(100, 110, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); atomicStampedReference.compareAndSet(110, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); } }); Thread tsf2 = new Thread(new Runnable() { @Override public void run() { int stamp = atomicStampedReference.getStamp(); try { TimeUnit.SECONDS.sleep(2); // 線程tsf1執行完
                } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "AtomicStampedReference:" + atomicStampedReference.compareAndSet(100, 120, stamp, stamp + 1)); } }); tsf1.start(); tsf2.start(); } }

運行結果:

自旋次數過長

 CAS是基於樂觀鎖的思想實現的,當頻繁出現當前值與所舊預期值不相等的狀況,會致使頻繁的自旋而使得浪費CPU資源。

只能保證單個共享變量的原子性

單純對共享變量進行CAS操做,只能保證單個,沒法使多個共享變量同時進行原子操做。

參考資料

狂神說Java:www.bilibili.com/video/BV1B7…
CAS機制及AtomicInteger源碼分析:juejin.im/post/5e2182…

相關文章
相關標籤/搜索