最近在看AtomicIntegerFieldUpdater的時(shí)候看到了兩個(gè)很有意思的方法:compareAndSet 和 weakCompareAndSet。下面主要針對這兩個(gè)方法展開討論蝴蜓。
基于 JDK 8
首先碟绑,我們知道AtomicIntegerFieldUpdater是一個(gè)基于反射的功能包,它可以實(shí)現(xiàn)針對于指定類中volatile int 字段的原子更新励翼。
『 compareAndSet 』:
/**
* Atomically sets the field of the given object managed by this updater
* to the given updated value if the current value {@code ==} the
* expected value. This method is guaranteed to be atomic with respect to
* other calls to {@code compareAndSet} and {@code set}, but not
* necessarily with respect to other changes in the field.
*
* @param obj An object whose field to conditionally set
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
* @throws ClassCastException if {@code obj} is not an instance
* of the class possessing the field established in the constructor
*/
public abstract boolean compareAndSet(T obj, int expect, int update);
以原子的方式更新這個(gè)更新器所管理的對象(obj)的成員變量蜈敢,并且將這個(gè)成員變量更新為給定的更新后的值(update)如果當(dāng)前值等于期望值(expect)時(shí)。
當(dāng)存在其他使用‘compareAndSet’或者’set’的情況下汽抚,這個(gè)方法可以確保是原子的抓狭,但如果你用其他的方式去改變這個(gè)成員變量時(shí)(如,使用直接賦值的方式 field=newField)造烁,那么它是不會(huì)遵循這個(gè)原子性的否过。
嗯,這個(gè)方法好理解惭蟋,compareAndSet保證了:a) 只有field的值為expect時(shí)苗桂;b) 將field的值修改為update的值;這兩步是原子完成的告组。同時(shí)field一定為一個(gè)volatile屬性煤伟,而volatile保證了屬性在線程間的可見性,以及防止了指令的重排序木缝。(關(guān)于volatile下面還會(huì)進(jìn)一步展開)便锨。嗯,一切看起來都挺美好的我碟。
然后放案,我們來看下另一個(gè)方法『weakCompareAndSet』:
/**
* Atomically sets the field of the given object managed by this updater
* to the given updated value if the current value {@code ==} the
* expected value. This method is guaranteed to be atomic with respect to
* other calls to {@code compareAndSet} and {@code set}, but not
* necessarily with respect to other changes in the field.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param obj An object whose field to conditionally set
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
* @throws ClassCastException if {@code obj} is not an instance
* of the class possessing the field established in the constructor
*/
public abstract boolean weakCompareAndSet(T obj, int expect, int update);
以原子的方式更新這個(gè)更新器所管理的對象(obj)的成員變量,并且將這個(gè)成員變量更新為給定的更新后的值(update)如果當(dāng)前值等于期望值(expect)時(shí)矫俺。
當(dāng)存在其他使用‘compareAndSet’或者’set’的情況下吱殉,這個(gè)方法可以確保是原子的,但如果你用其他的方式去改變這個(gè)成員變量時(shí)(如厘托,使用直接賦值的方式 field=newField)友雳,那么它是不會(huì)遵循這個(gè)原子性的。
該方法可能可能虛假的失敗并且不會(huì)提供一個(gè)排序的保證铅匹,所以它在極少的情況下用于代替compareAndSet方法沥阱。
第一次看weakCompareAndSet doc文檔的說明時(shí),我是困惑的伊群。我并不清楚你說的“fail spuriously”和“not provide ordering guarantees”的確切含義考杉。于是我查詢了些相關(guān)資料。
首先舰始,我從jdk 8 的官方文檔的java.util.concurrent.atomic上找到這么二段話:
The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.
一個(gè)原子類也支持weakCompareAndSet方法崇棠,該方法有適用性的限制。在一些平臺(tái)上丸卷,在正常情況下weak版本比compareAndSet更高效枕稀,但是不同的是任何給定的weakCompareAndSet方法的調(diào)用都可能會(huì)返回一個(gè)虛假的失敗( 無任何明顯的原因 )。一個(gè)失敗的返回意味著谜嫉,操作將會(huì)重新執(zhí)行如果需要的話萎坷,重復(fù)操作依賴的保證是當(dāng)變量持有expectedValue的值并且沒有其他的線程也嘗試設(shè)置這個(gè)值將最終操作成功。( 一個(gè)虛假的失敗可能是由于內(nèi)存沖突的影響沐兰,而和預(yù)期值(expectedValue)和當(dāng)前的值是否相等無關(guān) )哆档。此外weakCompareAndSet并不會(huì)提供排序的保證,即通常需要用于同步控制的排序保證住闯。然而瓜浸,這個(gè)方法可能在修改計(jì)數(shù)器或者統(tǒng)計(jì),這種修改無關(guān)于其他happens-before的程序中非常有用比原。當(dāng)一個(gè)線程看到一個(gè)通過weakCompareAndSet修改的原子變量時(shí)插佛,它不被要求看到其他變量的修改,即便該變量的修改在weakCompareAndSet操作之前量窘。
weakCompareAndSet atomically reads and conditionally writes a variable but does not create any happens-before orderings, so provides no guarantees with respect to previous or subsequent reads and writes of any variables other than the target of the weakCompareAndSet.
weakCompareAndSet實(shí)現(xiàn)了一個(gè)變量原子的讀操作和有條件的原子寫操作雇寇,但是它不會(huì)創(chuàng)建任何happen-before排序,所以該方法不提供對weakCompareAndSet操作的目標(biāo)變量以外的變量的在之前或在之后的讀或?qū)懖僮鞯挠行虮WC蚌铜。
這二段話是什么意思了锨侯,也就是說weakCompareAndSet底層不會(huì)創(chuàng)建任何happen-before的保證,也就是不會(huì)對volatile字段操作的前后加入內(nèi)存屏障厘线。因此就無法保證多線程操作下對除了weakCompareAndSet操作的目標(biāo)變量( 該目標(biāo)變量一定是一個(gè)volatile變量 )之其他的變量讀取和寫入數(shù)據(jù)的正確性识腿。
這里,需要對volatile進(jìn)行一個(gè)較為詳細(xì)的說明造壮。這樣大家就能更深刻的明白上面這段話的語義了渡讼。
volatile
volatile 的特性
volatile變量自身
具有下列特性:
① 可見性/一致性:對一個(gè) volatile 變量的讀,總是能看到(任意線程)對這個(gè) volatile 變量最后的寫入耳璧。
② 原子性:對任意單個(gè) volatile 變量的讀/寫具有原子性成箫,但類似于 volatile++這種復(fù)合操作不具有原子性。
Q:volatile是如何保證可見性的了旨枯?
A:在多核處理器中蹬昌,當(dāng)進(jìn)行一個(gè)volatile變量的寫操作時(shí),JIT編譯器生成的匯編指令會(huì)在寫操作的指令前加上一個(gè)“l(fā)ock”前綴攀隔≡矸罚“l(fā)ock”前綴的指令在多核處理器下會(huì)引發(fā)了兩件事情:
① 將當(dāng)前處理器緩存行的數(shù)據(jù)會(huì)寫回到系統(tǒng)內(nèi)存栖榨。
② 這個(gè)寫回內(nèi)存的操作會(huì)引起在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效。
因此更確切的來說明刷,因?yàn)椴僮骶彺娴淖钚挝粸橐粋€(gè)緩存行婴栽,所以每次對volatile變量自身的操作,都會(huì)使其所在緩存行的數(shù)據(jù)會(huì)寫回到主存中辈末,這就使得其他任意線程對該緩存行中變量的讀操作總是能看到最新寫入的值( 會(huì)從主存中重新載入該緩存行到線程的本地緩存中 )愚争。當(dāng)然,也正是因?yàn)榫彺婷看胃碌淖钚挝粸橐粋€(gè)緩存行挤聘,這導(dǎo)致在某些情況下程序可能出現(xiàn)“偽共享”的問題轰枝。嗯,好像有些個(gè)跑題组去,“偽共享”并不屬于本文范疇鞍陨,這里就不進(jìn)行展開討論。
好了添怔,目前為止我們已經(jīng)了解volatile變量自身所具有的特性了湾戳。注意,這里只是volatile自身所具有的特性广料,而volatile對線程的內(nèi)存
可見性的影響比volatile自身的特性更為重要砾脑。
volatile 寫-讀建立的 happens before 關(guān)系
happens-before 規(guī)則中有這么一條:
volatile變量規(guī)則:對一個(gè)volatile域的寫,happens-before于任意后續(xù)對這個(gè)volatile域的讀艾杏。
happens-before的這個(gè)規(guī)則會(huì)保證volatile寫-讀具有如下的內(nèi)存語義:
- volatile寫的內(nèi)存語義:
當(dāng)寫一個(gè) volatile 變量時(shí)韧衣,JMM 會(huì)把該線程對應(yīng)的本地內(nèi)存中的所有共享變量值刷新到主內(nèi)存。 - volatile讀的內(nèi)存語義:
當(dāng)讀一個(gè) volatile 變量時(shí)购桑,JMM 會(huì)把該線程對應(yīng)的本地內(nèi)存置為無效畅铭。線程接下來將從主內(nèi)存中讀取所有共享變量。
為了實(shí)現(xiàn) volatile 的內(nèi)存語義勃蜘,編譯器在生成字節(jié)碼時(shí)硕噩,會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。因?yàn)閮?nèi)存屏障是一組處理器指令缭贡,它并不由JVM直接暴露炉擅,因此JVM會(huì)根據(jù)不同的操作系統(tǒng)插入不同的指令以達(dá)成我們所要內(nèi)存屏障效果。
從整體執(zhí)行效率的角度考慮阳惹,JMM 選擇了在每個(gè) volatile 寫的后面插入一個(gè) StoreLoad 屏障谍失。
StoreLoad屏障
指令示例:Store1; StoreLoad; Load2
確保Store1數(shù)據(jù)對其他處理器變得可見(指刷新到內(nèi)存)先于Load2及所有后續(xù)裝載指令的裝載。StoreLoad Barriers會(huì)使該屏障之前的所有內(nèi)存訪問指令(存儲(chǔ)和裝載指令)完成之后莹汤,才執(zhí)行該屏障之后的內(nèi)存訪問指令快鱼。StoreLoad Barriers是一個(gè)“全能型”的屏障,它同時(shí)具有其他3個(gè)屏障的效果(LoadLoad Barriers、StoreStore Barriers抹竹、LoadStore Barriers)
好了线罕,到現(xiàn)在我們知道了volatile的內(nèi)存語義( happens-before關(guān)系 )會(huì)保證volatile寫操作之前的讀寫操作不會(huì)被重排序到volatile寫操作之后,并且保證了寫操作后將線程本地內(nèi)存(可能包含了多個(gè)緩存行)中所有的共享變量值都刷新到主內(nèi)存中柒莉。這樣其他線程總是能在volatile寫操作后的讀取操作中得到該線程中所有共享變量的正確值闻坚。這是volatile的happens-before關(guān)系( 通過內(nèi)存屏障實(shí)現(xiàn) )帶給我們的結(jié)果。注意兢孝,這個(gè)和volatile變量自身的特性是不同的,volatile自身僅僅是保證了volatile變量本身的可見性仅偎。而volatile的happens-before關(guān)系則保證了操作不會(huì)被重排序的同時(shí)保證了線程本地內(nèi)存中所有共享變量的可見性跨蟹。
好了,討論到這里橘沥,我們重新來理解下weakCompareAndSet的實(shí)現(xiàn)語義窗轩。也就是說,weakCompareAndSet操作僅保留了volatile自身變量的特性座咆,而出去了happens-before規(guī)則帶來的內(nèi)存語義痢艺。也就是說,weakCompareAndSet無法保證處理操作目標(biāo)的volatile變量外的其他變量的執(zhí)行順序( 編譯器和處理器為了優(yōu)化程序性能而對指令序列進(jìn)行重新排序 )介陶,同時(shí)也無法保證這些變量的可見性堤舒。
源碼實(shí)現(xiàn)
目前為止,我們已經(jīng)能夠明白compareAndSet方法和weakCompareAndSet方法的不同之處了哺呜。那么舌缤,接下來我們來看看這兩個(gè)方法的具體實(shí)現(xiàn):
public boolean compareAndSet(T obj, int expect, int update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
public boolean weakCompareAndSet(T obj, int expect, int update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
是的,你沒有看錯(cuò)某残。這兩個(gè)方法的實(shí)現(xiàn)完全一樣国撵。〔J『unsafe.compareAndSwapInt(obj, offset, expect, update);』中就是調(diào)用native方法了:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
這是因?yàn)椋?br> 在一些平臺(tái)上存在硬件的CAS單指令(即介牙,CAS使用一條機(jī)器指令就能完成),那么atomic_compare_exchange_weak和atomic_compare_exchange_strong本質(zhì)上就是一樣的了澳厢,因?yàn)樗鼈兌細(xì)w結(jié)為單指令cmpxchg(如x86上环础。??的例子就是x86系統(tǒng)的);但在不存在單一硬件的CAS指令的平臺(tái)上赏酥,atomic_compare_exchange_strong和atomic_compare_exchange_weak都是使用LL/SC(like ARM, PowerPC, etc)兩條匯編指令實(shí)現(xiàn)的喳整。??也就說它是多指令完成CAS的。那么就可能出現(xiàn)在LL 與 SC 兩條指令在執(zhí)行的間期發(fā)了上下文切換裸扶,或者其他加載和存儲(chǔ)操作框都,這都將導(dǎo)致一個(gè)store-conditional的spuriously fail。
基于JDK 9
在JDK 9中 compareAndSet 和 weakCompareAndSet方法的實(shí)現(xiàn)有些許的不同
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
① 底層調(diào)用的native方法的實(shí)現(xiàn)中,cmpxchgb指令前都會(huì)有“l(fā)ock”前綴了(在JDK 8中魏保,程序會(huì)根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴熬尺。只有在CPU是多處理器(multi processors)的時(shí)候,會(huì)添加一個(gè)lock前綴)谓罗。
② 同時(shí)多了一個(gè)@HotSpotIntrinsicCandidate注解粱哼,該注解是特定于Java虛擬機(jī)的注解。通過該注解表示的方法可能( 但不保證 )通過HotSpot VM自己來寫匯編或IR編譯器來實(shí)現(xiàn)該方法以提供性能檩咱。
它表示注釋的方法可能(但不能保證)由HotSpot虛擬機(jī)內(nèi)在化揭措。如果HotSpot VM用手寫匯編和/或手寫編譯器IR(編譯器本身)替換注釋的方法以提高性能,則方法是內(nèi)在的刻蚯。
也就是說雖然外面看到的在JDK9中weakCompareAndSet和compareAndSet底層依舊是調(diào)用了一樣的代碼绊含,但是不排除HotSpot VM會(huì)手動(dòng)來實(shí)現(xiàn)weakCompareAndSet真正含義的功能的可能性。
后記
嗯炊汹,關(guān)于compareAndSet與weakCompareAndSet兩個(gè)方法的不同躬充,看似可能是個(gè)“簡單”的問題,但當(dāng)我真的去探究它們的不同時(shí)讨便,還是話費(fèi)了我不少的時(shí)間充甚,同時(shí)也讓我對volatile有了更加深入的理解。這里關(guān)于CAS還有不少值得深入探討的地方霸褒,值得再用一篇文章好好的進(jìn)行敘說伴找。關(guān)于JDK9的改變也是值得以后慢慢去探索的。
參考
《Java 并發(fā)編程的藝術(shù)》