1. 目標:
從原碼層面分析CAS算法、以及java.util.concurrent.atomic 包下的原子類是如何運用CAS算法而實現線程安全。
2. 基礎知識
CAS算法基本原理
CAS算法全稱:compare and swap (比較並交換),是CPU指令級的操作,只有一步原子操作,整個操作是原子的,也就是要麼不執行,要麼執行完。這樣的系統原子操作能做什麼呢?比較內存中的參數值和方法調用處提供的參數值,如果相等,則將內存的參數值,設置爲新值,否則返回內存中的參數值。
volatile 關鍵字
這個關鍵字修飾的變量,能保證變量在線程之間可見。再解釋下,如果2個線程同時操作一個被volatile關鍵字修飾的變量,各個線程都會將變量從內存統一拷貝到自己的所在cpu的緩存中,當一個線程對變量進行修改之後,該變量位於其他線程的cpu緩存中拷貝能立即感知到。並同時調整爲一致。
Unsafe 類
大家都知道java一般很少直接操作內存,但是其實還是有一個類是可以直接操作修改內存的,他就是Unsafe。要獲取一個對象的某個字段的值是多少,我們應該知道幾個重要參數,第一個是對象在內存中的起始位置,第二個是需要獲取的字段在內存中的相對地址,第三個是字段的長度,也就是需要取幾個字節,纔對應這個字段的值。
3. 從源碼角度分析AtomicInteger是如何實現線程安全的
AtomicInteger的成員變量介紹
private static final Unsafe unsafe = Unsafe.getUnsafe();//用於操作內存的Unsafe實例
private static final long valueOffset;//AtomicInteger所保存的int值在內存中的偏移量
private volatile int value; //AtomicInteger中保存的int值,被volatile關鍵字修飾,確保線程之間可見。
static {
try {
//實際設置了int值在內存中的偏移
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
下面是我們徵對常用的方法進行介紹
//在當前值的基礎上自動+1,並且返回新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
這裏我們看底層是調用了unsafe的getAndAddInt
方法,我們跟進去
//obj 是AtomicInteger對象
//index long型的內存偏移量
//addVale 本次操作需要增加的值
public final int getAndAddInt(Object obj, long index, int addVale) {
int oldValue;
do {
oldValue = this.getIntVolatile(obj, index);//先從內存裏取的參數的值
} while(!this.compareAndSwapInt(obj, index, oldValue, oldValue + addVale));
return oldValue;//返回老值
}
//這個方法被final標記了,說明是一個本地方法,我們從openjdk裏面把相應的c語言代碼拿出來,主要要記住,下面的compareAndSwapInt方法是一個原子操作。
public final native boolean compareAndSwapInt(Object obj, long index, int oldValue, int newValue);
int compare_and_swap (int* reg, int oldval, int newval)
{
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
簡單分析上面的代碼,線程1和線程2並行修改值,一開始大家拿到的值都是55,線程1希望+1,改爲56,線程2也希望+1,但是如果在線程1先+1的情況下,線程再在55的基礎上+1,就沒有意義了,必須要在56的基礎上+1。我們逐行分析下代碼。
CAS算法缺點
CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題。
- 循環時間長開銷很大。
- 只能保證一個共享變量的原子操作。
- ABA問題