CAS 原理深入剖析,深入內(nèi)核源碼的那種
本文轉(zhuǎn)載自微信公眾號「KK架構(gòu)師」,作者wangkai。轉(zhuǎn)載本文請聯(lián)系KK架構(gòu)師公眾號。
一、CAS 簡介
CAS 的意思是 compare and swap,比較并交換。
CAS 的示意圖如下:
比如一個很簡單的操作,把變量 A = 2 加 1,結(jié)果為 3.
則先讀取 A 的當前值 E 為 2,在內(nèi)存計算結(jié)果 V 為 3,比較之前讀出來的 A 的當前值 2 和 最新值,如果最新值為 2 ,表示這個值沒有被別人改過,則放心的把最終的值更新為 3.
有一種情況是,在你更新結(jié)果之前,其他有個線程在中途把 A 更新成了 5 ,又更新回了 2。但是在當前線程看起來,沒有被改過。這就是 ABA 問題。
二、CAS 的實現(xiàn)
在 java 中,原子類都是用 cas 來實現(xiàn)的,我們可以看一看源碼。
- public final int getAndIncrement() {
- return unsafe.getAndAddInt(this, valueOffset, 1);
- }
發(fā)現(xiàn)是調(diào)用了 unsafe 類的 getAndAddInt,繼續(xù)看這個方法:
- 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;
- }
這里是一個 while 循環(huán),直到比較成功,來看一下這個 compareAndSwapInt 方法
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
發(fā)現(xiàn)這是一個 native 方法,意味著是 c 或者 c++ 寫的虛擬機的實現(xiàn)。
在網(wǎng)上找到了 HotSpot 虛擬機的源碼,找了一些資料發(fā)現(xiàn)了這個 compareAndSwapInt 的 c++ 代碼:
在 unsafe.cpp 這個文件的 Unsafe_CompareAndSwapInt 這個方法里
發(fā)現(xiàn)最終調(diào)用的是 Atomic:: cmpxchg 方法,我們再找到 atomic_linux_x86.inline.hpp 這個文件
其中有一句 LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
LOCK_IF_MP 是一個宏,這個宏的定義是:
mp 的意思是 multi processor,意思是在多核 cpu 上,要鎖一下這個指令。
到這里,結(jié)論是,最終調(diào)用了一條匯編指令:lock cmpxchg 指令,來實現(xiàn)底層 cas 的。
也就是 cpu 中有一條 cmpxchg 指令。
但是這條指令不是原子的,也就是拿出來和比較是兩個操作,中間有可能被別人打斷。
所以需要在這個過程加上 lock,意思是,我在對這個內(nèi)存操作的過程中,不允許被別人打斷。
可以簡單理解為把內(nèi)存總線鎖住,別人不允許修改這塊內(nèi)存。
三、ABA 問題的解決
很簡單,可以在數(shù)據(jù)上加上版本號即可,改了一次就新增一個版本號。
在 Java 中,可以使用 AtomicStampedReference 來解決這個問題
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.atomic.AtomicStampedReference;
- public class ABASingle {
- public static void main(String[] args) {
- AtomicInteger atomicInt = new AtomicInteger(100);
- atomicInt.compareAndSet(100, 101);
- atomicInt.compareAndSet(101, 100);
- System.out.println("new value = " + atomicInt.get());
- boolean result1 = atomicInt.compareAndSet(100, 101);
- System.out.println(result1); // result:true
- AtomicInteger v1 = new AtomicInteger(100);
- AtomicInteger v2 = new AtomicInteger(101);
- AtomicStampedReference<AtomicInteger> stampedRef = new AtomicStampedReference<AtomicInteger>(
- v1, 0);
- int stamp = stampedRef.getStamp();
- stampedRef.compareAndSet(v1, v2, stampedRef.getStamp(),
- stampedRef.getStamp() + 1);
- stampedRef.compareAndSet(v2, v1, stampedRef.getStamp(),
- stampedRef.getStamp() + 1);
- System.out.println("new value = " + stampedRef.getReference());
- boolean result2 = stampedRef.compareAndSet(v1, v2, stamp, stamp + 1);
- System.out.println(result2); // result:false
- }
- }