Java併發之CAS與原子類實現原理講解
一、CAS是什麼?
CAS的全稱是Compare and Swap,即比較並交換。比較的是當前記憶體中儲存的值與預期原值,交換的是新值與記憶體中的值。這個操作是硬體層面的指令,因此能夠保證原子性。Java通過JNI(本地方法呼叫)來使用這個原子操作,也是樂觀鎖最常用的機制。
CAS操作包含三個運算元——記憶體位置、預期原值和新值。在執行CAS操作時,先進行Compare操作,即比較記憶體位置的值與預期原值是否相等,若相等,則執行Swap操作將新值放入該記憶體位置。若不相等,則不進行Swap操作。
這時你一定會想,為什麼要這樣做,預期值和原值究竟從哪來?
我將通過一個例子講解,下面先考慮一下併發情況下的自增操作即如何實現。
如果把自增直接寫成i++,那一定會出現併發問題,因為這不是原子操作,就不多說了。但是有了CAS操作之後,併發環境下的自增操作就可以很安全的實現了。下面來看一下如何藉助CAS原子操作實現自增操作,先看一段虛擬碼。
//自旋直到CAS操作成功
do{
oldValue = getCurrent(addr);//在執行CAS之前獲取預期原值
newValue = oldValue + 1;//根據預期原值做增加操作的到新值
}while (!compareAndSwap(addr, oldValue, newValue));//執行CAS操作
(1)首先獲得預期原值,因為在自增情況下,新值是依賴於舊值的。
(2)通過計算得到新值。在這個過程中,可能有其他執行緒對該記憶體位置的值進行更新(自增),因為我們採用樂觀鎖的概念,並沒有對變數進行加鎖。
(3)再執行CAS操作。首先比較預期原值與當前記憶體位置的值是否相等。若相同,說明在這期間,沒有其他執行緒對該變數進行更新,沒有併發問題發生,則可以執行swap操作對舊值進行更新;若不同,則說明在這期間,有其他執行緒對變數進行了更新,當前的newValue其實是失效的,則要重新執行迴圈,即自旋,直至更新成功。
二、原子類AtomicInteger
原子類的自增、加法等操作底層都是通過自旋CAS操作實現的,其核心原理就是我上面寫的虛擬碼。
下面來看一下原始碼:
private static final Unsafe unsafe = Unsafe.getUnsafe();//最重要的Unsafe類,其中有對底層CAS操作的封裝的方法
private static final long valueOffset;//value相對於物件在記憶體中的偏移量,在執行CAS操作時需要用到
static {//初始化求偏移量
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final int getAndIncrement() {//自增操作
return unsafe.getAndAddInt(this, valueOffset, 1);//呼叫Unsafe物件的方法
}
UnSafe類中的重要方法:public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
這些方法都是native的,是呼叫了底層作業系統的CAS指令。
三、CAS操作的缺點
1、ABA。value初值為A,執行緒1讀取到預期原值為A,執行緒2讀取到預期原值為A,執行緒1通過CAS操作將A更新為B,再通過一次CAS操作將值更新為A,此時執行緒2進行CAS操作,在比較時發現預期原值A與當前記憶體位置的值A相同,則進行更新,但是此時value已經被更新過了,而不是原來那個A值了,這樣會產生執行緒安全問題。但是現在也有解決方案,在Java API中有一個類為AtomicStampedReference,該類在記錄預期原值的同時還會記錄標誌,在比較時還會比較標誌。
2、自旋時間長的情況下會導致很大開銷,若JVM支援pause指令,則可以解決此問題。pause指令有兩個作用,第一是延遲流水線執行指令,使得CPU不會消耗太多執行時間;第二是避免在退出迴圈時因記憶體順序衝突而引起CPU流水線被清空。
四、CAS與volatile
在Java的concurrent包中,有一種通用的實現方式,即CAS配合volatile來實現許多高併發類。
一般情況下實現流程:
(1)宣告變數為volatile
(2)使用CAS條件更新來實現執行緒之間的同步。
(3)使用volatile變數的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現執行緒之間的通訊。
Java併發包的框架如圖所示:
(圖片來自網路)
相關文章
- Java原子類實現原理分析Java
- CAS 演算法與 Java 原子類演算法Java
- java併發之SynchronousQueue實現原理Java
- Java 併發包原子操作類解析Java
- 盤點JAVA中基於CAS實現的原子類, 你知道哪些?Java
- Java併發(4)- synchronized與CASJavasynchronized
- 併發程式設計之 CAS 的原理程式設計
- 併發程式設計 — CAS 原理詳解程式設計
- Java併發/多執行緒-CAS原理分析Java執行緒
- Java併發程式設計之Java CAS操作Java程式設計
- Java原子類操作原理剖析Java
- Java中CAS演算法的集中體現:Atomic原子類庫,你瞭解嗎?Java演算法
- Java CAS 原理詳解Java
- 併發Lock之ReentrantLock實現原理ReentrantLock
- Java併發指南9:AQS共享模式與併發工具類的實現JavaAQS模式
- 併發容器之ArrayBlockingQueue和LinkedBlockingQueue實現原理詳解BloC
- CAS原子類:AtomicLongArray原始碼解析原始碼
- 譯文《Java併發程式設計之CAS》Java程式設計
- Java併發程式設計之原子變數Java程式設計變數
- 【Java】手把手理解CAS實現原理Java
- 「Java」手把手理解CAS實現原理Java
- 《java併發程式設計的藝術》原子操作類Java程式設計
- Java併發程式設計-CASJava程式設計
- Java併發之AQS原理剖析JavaAQS
- Java併發之原子性、有序性、可見性Java
- CAS 原子操作
- 多執行緒系列(十六) -常用併發原子類詳解執行緒
- Java ConcurrentHashMap 高併發安全實現原理解析JavaHashMap
- 最強幹貨:Java併發之AQS原理詳解JavaAQS
- CAS、原子操作類的應用與淺析及Java8對其的優化Java優化
- 用Java 19實現類似Go併發 - mccueJavaGo
- 《java併發程式設計的藝術》併發底層實現原理Java程式設計
- CPU實現原子操作的原理
- Java併發指南3:併發三大問題與volatile關鍵字,CAS操作Java
- java多執行緒與併發 - 併發工具類Java執行緒
- 詳解鎖原理,synchronized、volatile+cas底層實現synchronized
- Java併發程式設計之Executor執行緒池原理與原始碼解讀Java程式設計執行緒原始碼
- Java多執行緒併發工具類-訊號量Semaphore物件講解Java執行緒物件
- java高併發系列 - 第21天:java中的CAS操作,java併發的基石Java