什么是CAS
CAS的全稱為Compare-And-Swap,直譯就是對(duì)比交換爱葵。是一條CPU的原子指令河劝,其作用是讓CPU先進(jìn)行比較兩個(gè)值是否相等,然后原子地更新某個(gè)位置的值慎恒,經(jīng)過調(diào)查發(fā)現(xiàn)任内,其實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令,就是說CAS是靠硬件實(shí)現(xiàn)的融柬,JVM只是封裝了匯編調(diào)用死嗦,那些AtomicInteger類便是使用了這些封裝后的接口。
????簡單解釋:CAS操作需要輸入兩個(gè)數(shù)值粒氧,一個(gè)舊值(期望操作前的值)和一個(gè)新值越除,在操作期間先比較下在舊值有沒有發(fā)生變化,如果沒有發(fā)生變化外盯,才交換成新值摘盆,發(fā)生了變化則不交換。
????CAS操作是原子性的饱苟,所以多線程并發(fā)使用CAS更新數(shù)據(jù)時(shí)孩擂,可以不使用鎖。JDK中大量使用了CAS來更新數(shù)據(jù)而防止加鎖(synchronized 重量級(jí)鎖)來保持原子更新箱熬。
????相信sql大家都熟悉类垦,類似sql中的條件更新一樣:update set id=3 from table where id=2狈邑。因?yàn)閱螚lsql執(zhí)行具有原子性,如果有多個(gè)線程同時(shí)執(zhí)行此sql語句蚤认,只有一條能更新成功米苹。
如果不使用CAS,在高并發(fā)下烙懦,多線程同時(shí)修改一個(gè)變量的值我們需要synchronized加鎖(可能有人說可以用Lock加鎖驱入,Lock底層的AQS也是基于CAS進(jìn)行獲取鎖的)。
public class Test {
private int i=0;
public synchronized int add(){
return i++;
}
}
java中為我們提供了AtomicInteger 原子類(底層基于CAS進(jìn)行更新數(shù)據(jù)的)氯析,不需要加鎖就在多線程并發(fā)場(chǎng)景下實(shí)現(xiàn)數(shù)據(jù)的一致性亏较。
public class Test {
private AtomicInteger i = new AtomicInteger(0);
public int add(){
return i.addAndGet(1);
}
}
java.util.concurrent包都中的實(shí)現(xiàn)類都是基于volatile和CAS來實(shí)現(xiàn)的。尤其java.util.concurrent.atomic包下的原子類掩缓。
簡單介紹下volatile特性:
- 內(nèi)存可見性(當(dāng)一個(gè)線程修改volatile變量的值時(shí)雪情,另一個(gè)線程就可以實(shí)時(shí)看到此變量的更新值)
- 禁止指令重排(volatile變量之前的變量執(zhí)行先于volatile變量執(zhí)行,volatile之后的變量執(zhí)行在volatile變量之后)
AtomicInteger 源碼解析
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//用于獲取value字段相對(duì)當(dāng)前對(duì)象的“起始地址”的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
//返回當(dāng)前值
public final int get() {
return value;
}
//遞增加detla
public final int getAndAdd(int delta) {
//三個(gè)參數(shù)你辣,1巡通、當(dāng)前的實(shí)例 2、value實(shí)例變量的偏移量 3舍哄、當(dāng)前value要加上的數(shù)(value+delta)宴凉。
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//遞增加1
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}
我們可以看到 AtomicInteger 底層用的是volatile的變量和CAS來進(jìn)行更改數(shù)據(jù)的。
volatile保證線程的可見性表悬,多線程并發(fā)時(shí)弥锄,一個(gè)線程修改數(shù)據(jù),可以保證其它線程立馬看到修改后的值
CAS 保證數(shù)據(jù)更新的原子性蟆沫。
Unsafe源碼解析
下面分析下Unsafe 類中的實(shí)現(xiàn)籽暇。代碼反編譯出來的。
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
return l;
}
public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
return i;
}
public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
return l;
}
public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
{
Object localObject;
do
localObject = getObjectVolatile(paramObject1, paramLong);
while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
return localObject;
}
從源碼中發(fā)現(xiàn)饭庞,內(nèi)部使用自旋的方式進(jìn)行CAS更新(while循環(huán)進(jìn)行CAS更新戒悠,如果更新失敗,則循環(huán)再次重試)舟山。
又從Unsafe類中發(fā)現(xiàn)绸狐,原子操作其實(shí)只支持下面三個(gè)方法。
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
我們發(fā)現(xiàn)Unsafe只提供了3種CAS方法:compareAndSwapObject累盗、compareAndSwapInt和compareAndSwapLong六孵。都是native方法。
AtomicBoolean 源碼解析
public class AtomicBoolean implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
...
}
從AtomicBoolean源碼幅骄,發(fā)現(xiàn)他底層也是使用volatile類型的int 變量劫窒,跟AtomicInteger 實(shí)現(xiàn)方式一樣,只不過是把Boolean轉(zhuǎn)換成 0和1進(jìn)行操作拆座。
所以原子更新char主巍、float和double變量也可以轉(zhuǎn)換成int 或long來實(shí)現(xiàn)CAS的操作冠息。
CAS缺點(diǎn)
- ABA問題。因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化孕索,如果沒有發(fā)生變化則更新逛艰,但是如果一個(gè)值原來是A,變成了B搞旭,又變成了A散怖,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了肄渗。ABA問題的解決思路就是使用版本號(hào)镇眷。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一翎嫡,那么A-B-A 就會(huì)變成1A-2B-3A欠动。
從Java1.5開始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來解決ABA問題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用惑申,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志具伍,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值圈驼。 - 循環(huán)時(shí)間長開銷大人芽。自旋CAS如果長時(shí)間不成功,會(huì)給CPU帶來非常大的執(zhí)行開銷绩脆。
想了解更多精彩內(nèi)容請(qǐng)關(guān)注我的公眾號(hào)