基礎(chǔ)介紹
要對AtomicInteger有一個(gè)深入的認(rèn)識,就必須要了解一下悲觀鎖和樂觀鎖。
cpu是時(shí)分復(fù)用的,也就是把cpu的時(shí)間片,分配給不同的線程進(jìn)程輪流執(zhí)行,
時(shí)間片與時(shí)間片之間,需要進(jìn)行cpu切換,也就是會發(fā)生進(jìn)程的切換。切換涉及到清空
寄存器,緩存數(shù)據(jù)。然后重新加載新的thread所需數(shù)據(jù)。
當(dāng)一個(gè)線程被掛起時(shí),加入到阻塞隊(duì)列,在一定的時(shí)間或條件下,在通過
notify(),notifyAll()喚醒回來。在某個(gè)資源不可用的時(shí)候,就將cpu讓出,
把當(dāng)前等待線程切換為阻塞狀態(tài)。等到資源(比如一個(gè)共享數(shù)據(jù))可用了,那么就將線程喚醒,
讓他進(jìn)入runnable狀態(tài)等待cpu調(diào)度。這就是典型的悲觀鎖的實(shí)現(xiàn)。
但是,由于在進(jìn)程掛起和恢復(fù)執(zhí)行過程中存在著很大的開銷。當(dāng)一個(gè)線程正在等待鎖時(shí),
它不能做任何事,所以悲觀鎖有很大的缺點(diǎn)。舉個(gè)例子,如果一個(gè)線程需要某個(gè)資源,但是
這個(gè)資源的占用時(shí)間很短,當(dāng)線程第一次搶占這個(gè)資源時(shí),可能這個(gè)資源被占用,如果此時(shí)掛起
這個(gè)線程,可能立刻就發(fā)現(xiàn)資源可用,然后又需要花費(fèi)很長的時(shí)間重新?lián)屨兼i,時(shí)間代價(jià)
就會非常的高。
所以就有了樂觀鎖的概念,他的核心思路就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,
如果因?yàn)闆_突失敗就重試,直到成功為止。在上面的例子中,某個(gè)線程可以不讓出cpu,而是一直
while循環(huán),如果失敗就重試,直到成功為止。所以,當(dāng)數(shù)據(jù)爭用不嚴(yán)重時(shí),樂觀鎖效果更好。
比如我們要說的AtomicInteger底層同步CAS就是一種樂觀鎖思想的應(yīng)用。
CAS就是Compare and Swap的意思,比較并操作。很多的cpu直接支持CAS指令。CAS是項(xiàng)
樂觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,
而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。
CAS有3個(gè)操作數(shù),內(nèi)存值V,預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),
將內(nèi)存值V修改為B,否則什么都不做。
</br>
CAS操作
CAS通過調(diào)用JNI的代碼實(shí)現(xiàn)的。JNI:Java Native Interface為JAVA本地調(diào)用,允許java調(diào)用其他語言。而compareAndSwapInt就是借助C來調(diào)用CPU底層指令實(shí)現(xiàn)的。#lock類似的cpu指令.
AtomicInteger內(nèi)部有一個(gè)變量UnSafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();
Unsafe類是一個(gè)可以執(zhí)行不安全、容易犯錯(cuò)的操作的一個(gè)特殊類。雖然Unsafe類中所有方法都是public的,但是這個(gè)類只能在一些被信任的代碼中使用。其實(shí)CAS指的是sun.misc.Unsafe這個(gè)類中的一些方法的統(tǒng)稱。例如,Unsafe這個(gè)類中有compareAndSwapInt、compareAndSwapLong等方法。
public final native boolean compareAndSwapInt(Object o, long V, int E, int N);
??CAS的過程是:它包含了3個(gè)參數(shù)CAS(O,V,E,N)。O表示要更新的對象。V表示指明更新的對象中的哪個(gè)變量,E是進(jìn)行比較的值,如果V==E,則將N賦值給V。
??第二個(gè)參數(shù)V(offset),其實(shí)要更新的對象里的字段相對于對象初始位置的內(nèi)存偏移量。通俗一點(diǎn)就是在CAS(O,V,E,N)中,O是你要更新那個(gè)對象,
V就是我要通過這個(gè)偏移量找到這個(gè)對象中的value對象,來對他進(jìn)行操作。也就是說,如果我把1這個(gè)數(shù)字屬性更新到2的話,需要這樣調(diào)用:
compareAndSwapInt(this, valueOffset, 1, 2);
valueOffset字段表示內(nèi)存位置,可以在AtomicInteger對象中使用unsafe得到:
static {
try {
valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex); }
}
AtomicInteger內(nèi)部使用變量value表示當(dāng)前的整型值,這個(gè)整型變量還是volatile的,表示內(nèi)存可見性,一個(gè)線程修改value之后保證對其他線程的可見性:
private volatile int value;
AtomicInteger內(nèi)部還封裝了一下CAS,定義了一個(gè)compareAndSet方法,只需要2個(gè)參數(shù):
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
其中
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
類似于:
if (this == expect) {
this = update;
return true;
} else {
return false;
}
具體函數(shù)說明
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe(); //這里是初始化一個(gè)Unsafe對象。因?yàn)镃AS是這個(gè)類中的方法。
private static final long valueOffset;
static {
try {
/*一個(gè)java對象可以看成是一段內(nèi)存,各個(gè)字段都得按照一定的順序放在這段內(nèi)存里,
同時(shí)考慮到對齊要求,可能這些字段不是連續(xù)放置的,用這個(gè)方法能準(zhǔn)確地告訴你某個(gè)
字段(也就是下面的value字段)相對于對象的起始內(nèi)存地址的字節(jié)偏移量,因?yàn)槭窍鄬?
偏移量,所以它其實(shí)跟某個(gè)具體對象又沒什么太大關(guān)系,跟class的定義和虛擬機(jī)的內(nèi)
存模型的實(shí)現(xiàn)細(xì)節(jié)更相關(guān)。通俗一點(diǎn)就是在CAS(O,V,E,N)中,O是你要更新那個(gè)對象,
V就是我要通過這個(gè)偏移量找到這個(gè)對象中的value對象,來對他進(jìn)行操作。*/
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//volatile,保證變量的可見性。
private volatile int value;
//有參構(gòu)造
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
public final int getAndIncrement() {
//為什么會無限循環(huán),先得到當(dāng)前的值value,然后再把當(dāng)前的值加1
//加完之后使用cas原子操作讓當(dāng)前值加1處理正確。當(dāng)然cas原子操作不一定是成功的,
//所以做了一個(gè)死循環(huán),當(dāng)cas操作成功的時(shí)候返回?cái)?shù)據(jù)。這里由于使用了cas原子操作,
//所以不會出現(xiàn)多線程處理錯(cuò)誤的問題。
//比如,
// 1. Thread-A進(jìn)入循環(huán)獲取current=1,然后切下cpu,Thread-B上cpu得到current=1再下cpu;
// 2. 然后Thread-A的next值為2,進(jìn)行cas操作并且成功的時(shí)候,將value修改成了2;這時(shí)候內(nèi)存中value值為2了,
// 這個(gè)時(shí)候Thread-B切上cpu執(zhí)行的next值為2,當(dāng)進(jìn)行cas操作的時(shí)候由于expected值已經(jīng)是2,而不是1了;所以cas操作會失敗,
// 3. 失敗了怎么辦,在下個(gè)循環(huán)中得到的current就變成了2;也就不會出現(xiàn)多線程處理問題了!
for (;;) {
int current = get(); //得到當(dāng)前的值
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
CAS缺點(diǎn)
??CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。
---ABA問題,循環(huán)時(shí)間長開銷大和只能保證一個(gè)共享變量的原子操作。
??1. ABA問題。因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個(gè)值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時(shí)候把版本號加一,那么A-B-A 就會變成1A-2B-3A。
??2. 循環(huán)時(shí)間長開銷大。自旋CAS如果長時(shí)間不成功,會給CPU帶來非常大的執(zhí)行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率。
??3. 只能保證一個(gè)共享變量的原子操作。當(dāng)對一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個(gè)共享變量操作時(shí),循環(huán)CAS就無法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行CAS操作。