對 volatile、compareAndSet苍蔬、weakCompareAndSet 的一些思考

最近在看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ù)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末傲霸,一起剝皮案震驚了整個(gè)濱河市疆瑰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昙啄,老刑警劉巖穆役,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梳凛,居然都是意外死亡耿币,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門韧拒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淹接,“玉大人,你說我怎么就攤上這事叛溢∷艿浚” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵楷掉,是天一觀的道長厢蒜。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么斑鸦? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任愕贡,我火速辦了婚禮,結(jié)果婚禮上巷屿,老公的妹妹穿的比我還像新娘固以。我一直安慰自己,他們只是感情好嘱巾,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布憨琳。 她就那樣靜靜地躺著,像睡著了一般浓冒。 火紅的嫁衣襯著肌膚如雪栽渴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天稳懒,我揣著相機(jī)與錄音,去河邊找鬼慢味。 笑死场梆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纯路。 我是一名探鬼主播或油,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驰唬!你這毒婦竟也來了顶岸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤叫编,失蹤者是張志新(化名)和其女友劉穎辖佣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搓逾,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卷谈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霞篡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片世蔗。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖朗兵,靈堂內(nèi)的尸體忽然破棺而出污淋,到底是詐尸還是另有隱情,我是刑警寧澤余掖,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布寸爆,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏而昨。R本人自食惡果不足惜救氯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歌憨。 院中可真熱鬧着憨,春花似錦、人聲如沸务嫡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽心铃。三九已至准谚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間去扣,已是汗流浹背柱衔。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愉棱,地道東北人唆铐。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像奔滑,于是被迫代替她去往敵國和親艾岂。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容