CAS:Compare and Swap
CAS(V, E, N)
- V:要更新的變量
- E:預(yù)期值
- N:新值
如果V值等于E值佳遣,則將V值設(shè)為N值穿挨;如果V值不等于E值荠卷,說明其他線程做了更新豪筝,那么當(dāng)前線程什么也不做愚墓。(放棄操作或重新讀取數(shù)據(jù))
CAS屬于樂觀鎖输虱,多個線程競爭只會有一個勝出些楣,其余線程并不會掛起,而是繼續(xù)嘗試獲取更新,當(dāng)然也可以主動放棄愁茁。CAS操作是基于系統(tǒng)原語的(原語的執(zhí)行必須是連續(xù)的蚕钦,操作期間不會被系統(tǒng)中斷,是一條CPU的原子指令)鹅很,因此是一個不需要加鎖的鎖嘶居,也因此不可能出現(xiàn)死鎖的情況。也就是說無鎖操作天生免疫死鎖促煮。
Unsafe類
java中CAS操作依賴于Unsafe類邮屁,Unsafe類所有方法都是native的,直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應(yīng)任務(wù)菠齿,它可以像C一樣操作內(nèi)存指針佑吝,是非線程安全的。
Unsafe里的CAS 操作相關(guān)
Java中無鎖操作CAS基于以下3個方法實現(xiàn):
//第一個參數(shù)o為給定對象绳匀,offset為對象內(nèi)存的偏移量芋忿,通過這個偏移量迅速定位字段并設(shè)置或獲取該字段的值,
//expected表示期望值襟士,x表示要設(shè)置的值盗飒,下面3個方法都通過CAS原子指令執(zhí)行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
Atomic系列內(nèi)部方法是基于下述方法的實現(xiàn)的陋桂。
并發(fā)包中的原子操作類(Atomic系列)
并發(fā)包中的原子操作類提供了許多基于CAS實現(xiàn)的原子操作類逆趣。
原子更新基本類型
- AtomicBoolean:原子更新布爾類型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新長整型
下面看AtomicInteger類的源碼:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 獲取指針類Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
//下述變量value在AtomicInteger實例對象內(nèi)的內(nèi)存偏移量
private static final long valueOffset;
static {
try {
//通過unsafe類的objectFieldOffset()方法,獲取value變量在對象內(nèi)存中的偏移
//通過該偏移量valueOffset嗜历,unsafe類的內(nèi)部方法可以獲取到變量value對其進行取值或賦值操作
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//當(dāng)前AtomicInteger封裝的int變量value
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
//獲取當(dāng)前最新值
public final int get() {
return value;
}
//設(shè)置當(dāng)前值宣渗,具備volatile效果,方法用final修飾是為了更進一步的保證線程安全梨州。
public final void set(int newValue) {
value = newValue;
}
//最終會設(shè)置成newValue痕囱,使用該方法后可能導(dǎo)致其他線程在之后的一小段時間內(nèi)可以獲取到舊值,有點類似于延遲加載
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//設(shè)置新值并獲取舊值暴匠,底層調(diào)用的是CAS操作即unsafe.compareAndSwapInt()方法
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//如果當(dāng)前值為expect鞍恢,則設(shè)置為update(當(dāng)前值指的是value變量)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//當(dāng)前值加1返回舊值,底層CAS操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//當(dāng)前值減1每窖,返回舊值帮掉,底層CAS操作
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//當(dāng)前值增加delta,返回舊值窒典,底層CAS操作
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//當(dāng)前值加1蟆炊,返回新值,底層CAS操作
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//當(dāng)前值減1瀑志,返回新值涩搓,底層CAS操作
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
//當(dāng)前值增加delta污秆,返回新值,底層CAS操作
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
//省略一些不常用的方法....
}
AtomicInteger基本是基于Unsafe類中CAS相關(guān)操作實現(xiàn)的昧甘,是無鎖操作良拼。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
調(diào)用了Unsafe類中的getAndAddInt()方法,該方法執(zhí)行一個CAS操作疾层,保證線程安全将饺。
//Unsafe類中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
可看出getAndAddInt通過一個while循環(huán)不斷的重試更新要設(shè)置的值,直到成功為止痛黎,調(diào)用的是Unsafe類中的compareAndSwapInt方法予弧,是一個CAS操作方法。
上述源碼分析是基于JDK1.8的湖饱,如果是1.8之前的方法掖蛤,1.8之前的方法實現(xiàn)如下:
//JDK 1.7的源碼,由for的死循環(huán)實現(xiàn)井厌,并且直接在AtomicInteger實現(xiàn)該方法蚓庭,
//JDK1.8后,該方法實現(xiàn)已移動到Unsafe類中仅仆,直接調(diào)用getAndAddInt方法即可
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
CAS操作中可能會帶來的ABA問題
假設(shè)這樣一種場景器赞,當(dāng)?shù)谝粋€線程執(zhí)行CAS(V,E,U)操作,在獲取到當(dāng)前變量V墓拜,準(zhǔn)備修改為新值U前港柜,另外兩個線程已連續(xù)修改了兩次變量V的值,使得該值又恢復(fù)為舊值:
無法正確判斷這個變量是否已被修改過咳榜,一般稱這種情況為ABA問題夏醉。
ABA問題一般不會有太大影響,產(chǎn)生幾率也比較小涌韩。但是并不排除極特殊場景下會造成影響畔柔,因此需要解決方法:
- AtomicStampedReference類
- AtomicMarkableReference類
AtomicStampedReference類
一個帶有時間戳的對象引用,每次修改時臣樱,不但會設(shè)置新的值靶擦,還會記錄修改時間。在下一次更新時雇毫,不但會對比當(dāng)前值和期望值奢啥,還會對比當(dāng)前時間和期望值對應(yīng)的修改時間,只有二者都相同嘴拢,才會做出更新。解決了反復(fù)讀寫時寂纪,無法預(yù)知值是否已被修改的窘境席吴。
底層實現(xiàn)為:一個鍵值對Pair存儲數(shù)據(jù)和時間戳赌结,并構(gòu)造volatile修飾的私有實例;兩者都符合預(yù)期才會調(diào)用Unsafe的compareAndSwapObject方法執(zhí)行數(shù)值和時間戳替換孝冒。
AtomicMarkableReference類
一個boolean值的標(biāo)識柬姚,true和false兩種切換狀態(tài)表示是否被修改。不靠譜庄涡。
自旋鎖
自旋鎖:假設(shè)在不久的將來量承,線程可以獲得鎖,虛擬機會讓線程做幾個空循環(huán)(自旋)穴店,若干次循環(huán)撕捍、嘗試獲得鎖后,如果獲得鎖泣洞,線程進入臨界區(qū)忧风;如果沒有成功,則被掛起球凰。
避免了線程在嘗試獲得鎖失敗后狮腿,掛起,再次嘗試之間呕诉,狀態(tài)不斷轉(zhuǎn)換造成的資源浪費缘厢,提升效率。
但如果有太多線程進入自旋狀態(tài)甩挫,CPU的占用時間(自選過程)會過長贴硫,反而使得效率變低,性能下降捶闸。因此夜畴,虛擬機限制了自旋次數(shù)(幾十次至100次),這之后虛擬機會將線程掛起删壮,讓出cpu資源贪绘。