欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

為什么Java volatile++不是原子性的詳解

 更新時(shí)間:2021年02月01日 09:18:40   作者:dm_vincent  
這篇文章主要給大家介紹了關(guān)于為什么Java volatile++不是原子性的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

問題

在討論原子性操作時(shí),我們經(jīng)常會(huì)聽到一個(gè)說法:任意單個(gè)volatile變量的讀寫具有原子性,但是volatile++這種操作除外。

所以問題就是:為什么volatile++不是原子性的?

答案

因?yàn)樗鼘?shí)際上是三個(gè)操作組成的一個(gè)符合操作。

  1. 首先獲取volatile變量的值
  2. 將該變量的值加1
  3. 將該volatile變量的值寫會(huì)到對(duì)應(yīng)的主存地址

一個(gè)很簡(jiǎn)單的例子:

如果兩個(gè)線程在volatile讀階段都拿到的是a=1,那么后續(xù)在線程對(duì)應(yīng)的CPU核心上進(jìn)行自增當(dāng)然都得到的是a=2,最后兩個(gè)寫操作不管怎么保證原子性,結(jié)果最終都是a=2。每個(gè)操作本身都沒啥問題,但是合在一起,從整體上看就是一個(gè)線程不安全的操作:發(fā)生了兩次自增操作,然而最終結(jié)果卻不是3。

分析

結(jié)合內(nèi)存屏障這個(gè)概念對(duì)volatile的讀寫操作深入理解的話:

第一步:讀

在第一步操作的指令后,會(huì)增加兩個(gè)內(nèi)存屏障:

  1. 在Volatile讀操作后插入LoadLoad屏障,防止前面的Volatile讀與后面的普通讀重排序
  2. 在Volatile讀操作后插入LoadStore屏障,防止前面的Volatile讀與后面的普通寫重排序

因此第一個(gè)指令和它后續(xù)的普通讀寫操作會(huì)被保證沒有重排序來?yè)v亂。通常是去內(nèi)存中去讀。

那么問題又來了,為什么通常去內(nèi)存中讀?

其實(shí)這個(gè)問題要說細(xì)的話可以很細(xì),大概就兩個(gè)關(guān)鍵點(diǎn)吧:

  1. volatile的寫操作的緩存失效機(jī)制
  2. 最后一個(gè)對(duì)volatile變量執(zhí)行寫操作的CPU,由于在它對(duì)應(yīng)的緩存中保有最新的值,因此可以不用再去主存里面獲取

具體看下面第三步的分析。

第二步:自增

這個(gè)步驟沒什么特別的,就是在CPU自身的高速緩存(寄存器,L1-L3 Cache)中完成。不涉及到緩存和內(nèi)存的交互。

第三步:寫

volatile寫算是一個(gè)重點(diǎn)。

根據(jù)JMM對(duì)于volatile變量類型的語(yǔ)義規(guī)范:volatile在編譯之后,會(huì)在變量寫操作時(shí)添加LOCK前綴指令。這個(gè)LOCK前綴指令在多核處理器的環(huán)境中,有這樣的作用:

  1. 通知CPU將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)主存中
  2. 該寫回操作將使其他CPU緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效

另外,內(nèi)存屏障在volatile的寫操作中起到了很大的作用,來保證上面兩點(diǎn)能夠?qū)崿F(xiàn):

  1. 在Volatile寫操作前插入StoreStore屏障,防止前面其他寫與本次Volatile寫重排序
  2. 在Volatile寫操作后插入StoreLoad屏障,防止本次的Volatile寫與后面的讀操作重排序

延伸

那么為了解決volatile++這類復(fù)合操作的原子性,有什么方案呢?其實(shí)方案也比較多的,這里提供兩種典型的:

  1. 使用synchronized關(guān)鍵字
  2. 使用AtomicInteger/AtomicLong原子類型

synchronized關(guān)鍵字

synchronized是比較原始的同步手段。它本質(zhì)上是一個(gè)獨(dú)占的,可重入的鎖。當(dāng)一個(gè)線程嘗試獲取它的時(shí)候,可能會(huì)被阻塞住,所以高并發(fā)的場(chǎng)景下性能存在一些問題。

在某些場(chǎng)景下,使用synchronized關(guān)鍵字和volatile是等價(jià)的:

  1. 寫入變量值時(shí)候不依賴變量的當(dāng)前值,或者能夠保證只有一個(gè)線程修改變量值。
  2. 寫入的變量值不依賴其他變量的參與。
  3. 讀取變量值時(shí)候不能因?yàn)槠渌蜻M(jìn)行加鎖。

加鎖可以同時(shí)保證可見性和原子性,而volatile只保證變量值的可見性。

AtomicInteger/AtomicLong

這類原子類型比鎖更加輕巧,比如AtomicInteger/AtomicLong分別就代表了整型變量和長(zhǎng)整型變量。

在它們的實(shí)現(xiàn)中,實(shí)際上分別使用的volatile int/volatile long保存了真正的值。因此,也是通過volatile來保證對(duì)于單個(gè)變量的讀寫原子性的。

在此基礎(chǔ)之上,它們提供了原子性的自增自減操作。比如incrementAndGet方法,這類方法相對(duì)于synchronized的好處是:它們不會(huì)導(dǎo)致線程的掛起和重新調(diào)度,因?yàn)樵谄鋬?nèi)部使用的是CAS非阻塞算法。

CAS是什么

所謂的CAS全程為CompareAndSet。直譯過來就是比較并設(shè)置。這個(gè)操作需要接受三個(gè)參數(shù):

  1. 內(nèi)存位置
  2. 舊的預(yù)期值
  3. 新值

這個(gè)操作的做法就是看指定內(nèi)存位置的值符不符合舊的預(yù)期值,如果符合的話就將它替換成新值。它對(duì)應(yīng)的是處理器提供的一個(gè)原子性指令 - CMPXCHG。

比如AtomicLong的自增操作:

public final long incrementAndGet() {
 for (;;) {
  long current = get(); // Step 1
  long next = current + 1; // Step 2
  if (compareAndSet(current, next)) // Step 3
   return next;
 }
}

public final boolean compareAndSet(long expect, long update) {
 return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}

我們考慮兩個(gè)線程T1和T2,同時(shí)執(zhí)行到了上述Step 1處,都拿到了current值為1。然后通過Step 2之后,current在兩個(gè)線程中都被設(shè)置為2。

緊接著,來到Step 3。假設(shè)線程T1先執(zhí)行,此時(shí)符合CompareAndSet的設(shè)置規(guī)則,因此內(nèi)存位置對(duì)應(yīng)的值被設(shè)置成2,線程T1設(shè)置成功。當(dāng)線程T2執(zhí)行的時(shí)候,由于它預(yù)期current為1,但是實(shí)際上已經(jīng)變成了2,所以CompareAndSet執(zhí)行不成功,進(jìn)入到下一輪的for循環(huán)中,此時(shí)拿到最新的current值為2,如果沒有其它線程感染的話,再次執(zhí)行CompareAndSet的時(shí)候就能夠通過,current值被更新為3。

所以不難發(fā)現(xiàn),CAS的工作主要依賴于兩點(diǎn):

  1. 無(wú)限循環(huán),需要消耗部分CPU性能
  2. CPU原子指令CompareAndSet

雖然它需要耗費(fèi)一定的CPU Cycle,但是相比鎖而言還是有其優(yōu)勢(shì),比如它能夠避免線程阻塞引起的上下文切換和調(diào)度。這兩類操作的量級(jí)明顯是不一樣的,CAS更輕量一些。

總結(jié)

我們說對(duì)于volatile變量的讀/寫操作是原子性的。因?yàn)閺膬?nèi)存屏障的角度來看,對(duì)volatile變量的單純讀寫操作確實(shí)沒有任何疑問。

由于其中摻雜了一個(gè)自增的CPU內(nèi)部操作,就造成這個(gè)復(fù)合操作不再保有原子性。

然后,討論了如何保證volatile++這類操作的原子性,比如使用synchronized或者AtomicInteger/AtomicLong原子類。

到此這篇關(guān)于為什么Java volatile++不是原子性的文章就介紹到這了,更多相關(guān)Java volatile++不是原子性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java通過工廠、Map容器創(chuàng)建對(duì)象的方法

    Java通過工廠、Map容器創(chuàng)建對(duì)象的方法

    這篇文章主要介紹了Java通過工廠、Map容器創(chuàng)建對(duì)象的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • Maven 生成打包可執(zhí)行jar包的方法步驟

    Maven 生成打包可執(zhí)行jar包的方法步驟

    這篇文章主要介紹了Maven 生成打包可執(zhí)行jar包的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 詳解Java-Jackson使用

    詳解Java-Jackson使用

    這篇文章主要介紹了Java-Jackson使用詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • Java FutureTask類使用案例解析

    Java FutureTask類使用案例解析

    這篇文章主要介紹了Java FutureTask類使用案例解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值析,需要的朋友可以參考下
    2020-04-04
  • Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能詳解

    Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能詳解

    這篇文章主要介紹了Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能,較為詳細(xì)的描述了建造者模式的概念、原理并結(jié)合實(shí)例形式分析了Java使用建造者模式實(shí)現(xiàn)的辦理手機(jī)套餐功能具體步驟與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2018-05-05
  • Jdk11使用HttpClient提交Http2請(qǐng)求的實(shí)現(xiàn)方法

    Jdk11使用HttpClient提交Http2請(qǐng)求的實(shí)現(xiàn)方法

    這篇文章主要介紹了Jdk11使用HttpClient提交Http2請(qǐng)求的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • 使用maven插件對(duì)java工程進(jìn)行打包過程解析

    使用maven插件對(duì)java工程進(jìn)行打包過程解析

    這篇文章主要介紹了使用maven插件對(duì)java工程進(jìn)行打包過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • SpringBoot通過Nginx代理獲取真實(shí)IP

    SpringBoot通過Nginx代理獲取真實(shí)IP

    springboot作為后臺(tái)代碼,獲取到的登錄IP是前臺(tái)的代理服務(wù)器地址,并不是用戶的真實(shí)IP地址,本文主要介紹了SpringBoot通過Nginx代理獲取真實(shí)IP,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Java中保留兩位小數(shù)的四種方法實(shí)現(xiàn)實(shí)例

    Java中保留兩位小數(shù)的四種方法實(shí)現(xiàn)實(shí)例

    今天小編就為大家分享一篇關(guān)于Java中保留兩位小數(shù)的四種方法實(shí)現(xiàn)實(shí)例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • 淺談Spring Boot中如何干掉if else的方法

    淺談Spring Boot中如何干掉if else的方法

    這篇文章主要介紹了Spring Boot中如何干掉if else的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09

最新評(píng)論