引言
在介紹CAS
之前背零,我們有必要先理解線程安全的三大特性
- 原子性: 對于涉及共享變量訪問的操作刹缝,該操作從其執(zhí)行線程以外的任意線程來看是不可分割的吧恃,從而可以讓各個線程依次串行訪問亩钟,但是原子性并不保證可見性
- 可見性: 修改共享變量時搓侄,立即將工作內(nèi)存中的值同步到主存中焙蚓,并使該修改對其他線程可見
- 有序性: 禁止讀取共享變量后的代碼纹冤、修改共享變量前的代碼重排序
CAS
即compare and swap
的縮寫,中文翻譯成比較并交換购公。是一種用于在多線程環(huán)境下實現(xiàn)同步功能的機制萌京。調(diào)用Java CAS
需要三個操作數(shù)
- 內(nèi)存中值的內(nèi)存位置
- 預(yù)期值
- 新值
具體實現(xiàn)是通過值的內(nèi)存位置取到內(nèi)存中的值并與預(yù)期值比較,若相等宏浩,則將內(nèi)存位置處的值替換為新值知残,若不相等,則不做任何操作返回false比庄。如果大家有了解過悲觀鎖和樂觀鎖求妹,可以發(fā)現(xiàn)CAS
其實是一種樂觀鎖的實現(xiàn)乏盐。
使用CAS的目的
對于實現(xiàn)線程安全,我們用的比較多的應(yīng)該是synchronized
關(guān)鍵字制恍,synchronized
其實是一種悲觀鎖父能,鎖被占用的情況會導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖净神。CAS
是一種樂觀鎖何吝,每次取數(shù)據(jù)都不會加鎖,更新的時候會進行數(shù)據(jù)比對鹃唯,有沖突的話則會自旋重試岔霸。可以看到在讀操作頻繁俯渤,更新頻率低呆细,沖突概率低的情況下,用CAS
的話會更加合理八匠。當然JDK1.6之后絮爷,Java對synchronized
關(guān)鍵字來了一大波優(yōu)化(自旋鎖,鎖消除梨树,鎖粗化坑夯,偏向鎖,輕量級鎖)抡四,一般情況下使用synchronized
是非常穩(wěn)定的柜蜈。
CAS的底層實現(xiàn)
現(xiàn)在的CPU都是多核心的,多個核心通過總線來操作內(nèi)存指巡。那么這里就存在一個問題淑履,就是如果多個核心同時操作一塊內(nèi)存區(qū)域,會發(fā)生什么問題呢藻雪?是的秘噪,這里數(shù)據(jù)就會出現(xiàn)混亂。不過這里我們可以從intel
的使用手冊中找到答案勉耀,對指令加lock
前綴可以保證操作的原子性指煎,可見性以及有序性。好了便斥,底層的就不多說了至壤,我們直接去看一下java.util.concurrent.atomic
包下的原子類 AtomicInteger
的源碼實現(xiàn)
AtomicInteger源碼
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value值的內(nèi)存位置
private static final long valueOffset;
static {
try {
// 獲取value的內(nèi)存位置
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// value值 volatile 修飾 保證可見性
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
/**
* 獲取value在內(nèi)存中當前的值
*
* @return the current value
*/
public final int get() {
return value;
}
/**
* 比較并替換 實現(xiàn)在unsafe.compareAndSwapInt中
* @param expect 期望值
* @param update 新值
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* 自增 實現(xiàn)在unsafe.getAndAddInt中
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
- Unsafe.class
public final native boolean compareAndSwapInt(Object o, long valueOffset, int expected, int value);
/**
* 獲取當前值 并加1,返回的是加1前的值
*/
public final int getAndAddInt(Object o, long valueOffset, int addValue) {
int currentValue;
do {
currentValue = this.getIntVolatile(o, valueOffset);
// 比較當前內(nèi)存的值和預(yù)期值currentValue是否一致枢纠,一致的話則設(shè)置新值像街。但是因為當前內(nèi)存中的值有可能被其他線程修改,會有和預(yù)期值不一致的情況,所以這里會循環(huán)直到 compareAndSwapInt 返回成功為止宅广,這里的操作也稱為CAS自旋
} while(!this.compareAndSwapInt(o, valueOffset, currentValue, value + addValue));
return value;
}
AtomicInteger
是Java
對Integer
類型原子性操作的實現(xiàn)葫掉,可以看到底層都是調(diào)用了CAS compareAndSwapInt
native方法。
這里主要看一下compareAndSwapInt(Object o, long valueOffset, int expect, int update)
的四個參數(shù)
- o 當前操作的對象
- valueOffset 操作值所在的內(nèi)存位置
- expect 期望值
- update 新值
具體實現(xiàn)是將內(nèi)存位置處的數(shù)值與預(yù)期數(shù)值相比較跟狱,若相等俭厚,則將內(nèi)存位置處的值替換為新值。若不相等驶臊,則不做任何操作挪挤。
CAS自旋
指的是替換新值失敗時會進入循環(huán),重新獲取期望值关翎,直到期望值和內(nèi)存位置處的數(shù)值相等扛门。
CAS的問題
ABA問題
提到CAS
存在的問題,就不得不提ABA
問題纵寝,什么是ABA
問題呢论寨?
舉個例子,A是個共享變量爽茴,原值是10
葬凳,線程1從內(nèi)存中拿到了A,此時值為10
室奏,當線程1要對變量A進行CAS
操作前火焰,因為其他線程的操作,A從10
變?yōu)榱?code>11胧沫,又從11
變回了10
昌简。此時線程1對變量A執(zhí)行CAS
操作照道理應(yīng)該是要失敗的,但實際卻是成功的绒怨。這是因為經(jīng)過了上面的流程纯赎,在線程1看來,變量A沒有發(fā)生任何變化窖逗,所以它執(zhí)行CAS
操作是會成功的址否。
要解決ABA
問題餐蔬,通常的解決方案給對象加上版本號碎紊,每經(jīng)過一次CAS
操作就更新一次版本號
總結(jié)
本文的目的主要是讓自己對java并發(fā)包的基礎(chǔ)CAS
有個簡單的了解,以便進行后續(xù)的源碼分析