CAS(Compare And Swap)
是非阻塞同步的實現原理,它是CPU硬件層面的一種指令;
CAS制定操作包含三個參數
- 內存值(內存地址)v
- 預期值E
- 新增值N
當CAS指令執行時,當且僅當預期值E和內存值V相同時,才更新內存值為N,否則不更新;
上述的處理過程是一個原子操作;
CAS 可以看作是它們合并后的整體一個不可分割的原子操作,并且其原子性是直接在硬件層面得到保障的。
CAS的應用場景
再juc的atomic相關類,AQS,currentHashMap等是線上有廣泛的應用
在并發編程中最容易出現的是線程安全的問題,i++在多線程的時候,就無法得到正確的值;而使用synchronized又不能高性能的解決問題;
actomic包提供了一組原子操作類
- 基本類型:AtomicInteger,AtomicLong,AtomicBoolean
- 引用類型:AtomicReferenc、AtomicStampedReference
- 數組類型:AtomicIntegerArray
- 原子累加器LongAdder,Striped64
原子更新基本類型
以AtomicInteger為例總結常用的方法
//以原子的方式將實例中的原值加1,返回的是自增前的舊值;
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}//getAndSet(int newValue):將實例中的值更新為新值,并返回舊值;public final boolean getAndSet(boolean newValue) {boolean prev;do {prev
= get();} while (!compareAndSet(prev, newValue));return prev;}//incrementAndGet() :以原子的方式將實例中的原值進行加1操作,并返回最終相加后的結果;public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}//addAndGet(int delta) :以原子方式將輸入的數值與實例中原本的值相加,并返回最后的結果;public final int addAndGet(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
通過CAS自增實現,如果CAS失敗,自旋直到成功+1。
LongAdder
是LongAdder引入的初衷——解決高并發環境下AtomicInteger,AtomicLong的自旋瓶頸問題。
設計思路
AtomicLong中有個內部變量value保存著實際long值,所有的操作都是針對該變量進行。也就是說高并發的環境下,value變量其實是一個熱點,也就是N個線程競爭一個熱點。LongAdder的基本思路就是分散熱點,將value值分散到一個數組中,不同線程會命中到數組的不同槽中,各個線程只對自己槽中的那個值進行CAS操作,這樣熱點就被分散了,沖突的概率小很多。如果要獲取真正的long值,只要將各個槽點的變量值累加返回;
LongAdder的內部結構
LongAdder內部有一個base變量,一個Cell[]數組:
base變量:非競態條件下,直接累加到該變量上
Cell[]數組:競態條件下,累加個各個線程自己的槽Cell[i]中
CAS缺陷
CAS 雖然高效地解決了原子操作,但是還是存在一些缺陷的,主要表現在三個方面:
- 自旋 CAS 長時間不成功,則會給 CPU 帶來非常大的開銷
- 只能保證一個共享變量原子操作
- ABA 問題
ABA問題的描述和解決方案
描述 :當有多個線程對一個原子類進行操作的時候,某個線程在短時間內將原子類的值A修改為B,又馬上將其修改為A,此時其他線程不感知,還是會修改成功。
ABA問題的解決方案
數據庫有個鎖稱為樂觀鎖,是一種基于數據版本實現數據同步的機制,每次修改一次數據,版本就會進行累加。
同樣,Java也提供了相應的原子引用類AtomicStampedReference, reference即我們實際存儲的變量,stamp是版本,每次修改可以通過+1保證版本唯一性。這樣就可以保證每次修改后的版本也會往上遞增