最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深度理解Java中volatile的內(nèi)存語(yǔ)義

 更新時(shí)間:2021年06月16日 15:08:19   作者:GDUT_Ember  
前面我們已經(jīng)講過了volatile的作用、底層實(shí)現(xiàn)與內(nèi)存屏障,下面就總結(jié)一下整個(gè)流程,文中有非常詳細(xì)的介紹及圖文示例,需要的朋友可以參考下

volatile可見性實(shí)驗(yàn)

舉個(gè)栗子

在這里插入圖片描述

我這里開了兩個(gè)線程,后面的線程去修改volatile變量,前面的線程不斷獲取volatile變量,

結(jié)果是會(huì)一致卡在死循環(huán),控制臺(tái)沒有任何輸出

假如將flag讓volatile來(lái)進(jìn)行修飾

在這里插入圖片描述

結(jié)果是:三秒后,就不會(huì)不斷打印出信息出來(lái)

注意,Thread.sleep是會(huì)刷新線程內(nèi)存的,所以不要使用Thread.sleep來(lái)分別讓一個(gè)線程獲取兩次volatile變量

volatile的特性

volatile其實(shí)相當(dāng)于對(duì)變量的單詞讀或?qū)懖僮骷恿随i、做了同步

由于是加了鎖,所以就有前面提到的鎖的語(yǔ)義,即鎖的happens-before,鎖的happens-before規(guī)定了釋放鎖的操作對(duì)于后續(xù)獲得鎖操作是可見的,所以釋放鎖的線程對(duì)于后續(xù)獲得鎖的線程是可見的,意味著volatile修飾的變量的最后寫入是可以被后面獲得鎖的線程讀取的

32位的操作系統(tǒng)去操作64位的變量時(shí),會(huì)分成高32位和低32位去執(zhí)行,但由于鎖,會(huì)導(dǎo)致這個(gè)操作也是具有原子性的,因?yàn)?strong>鎖的語(yǔ)義決定了臨界區(qū)代碼的執(zhí)行具有原子性,即必須要整個(gè)代碼塊執(zhí)行完,如果沒有鎖,那么就不是原子性的,可能會(huì)被分成不連續(xù)的兩步來(lái)執(zhí)行

所以,volatile變量自身是具有下面特性的

1.原子性:無(wú)論多大的變量,對(duì)其單詞讀或?qū)懖僮鞫际蔷哂性有缘模绻愃朴趇++這種操作就不具備原子性了,因?yàn)檫@本來(lái)就是兩條命令

2.可見性:操作volatile變量的線程是可以獲取前一個(gè)線程對(duì)其的修改,即當(dāng)前線程總是可以看到volatile變量最后的寫入

volatile 寫與讀的內(nèi)存語(yǔ)義

我們先來(lái)研究一下什么依賴關(guān)系需要volatile

前面提到過總共有三種依賴關(guān)系

  • 讀后寫
  • 寫后讀
  • 寫后寫

volatile是實(shí)現(xiàn)可見性的,所以寫后寫就不用考慮了,而且讀后寫是不需要可見性的,所以需要可見性的是寫后讀

寫語(yǔ)義

volatile寫的內(nèi)存語(yǔ)義如下:

當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存(即不僅修改了本地內(nèi)存,而且還刷新到了主內(nèi)存),注意,這個(gè)刷新是按緩存行的形式(64字節(jié))

舉個(gè)栗子

兩個(gè)線程,A線程修改flag與A,flag與A原本為默認(rèn)值

在這里插入圖片描述

所以volatile的寫是有兩個(gè)操作的,然后這兩個(gè)操作會(huì)合成一個(gè)原子操作

讀語(yǔ)義

volatile的讀內(nèi)存語(yǔ)義為:當(dāng)讀一個(gè)volatile變量時(shí),JVM會(huì)把線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效,接下來(lái)重新去主內(nèi)存中讀取共享變量,并且更新本地內(nèi)存,注意:是讀的時(shí)候會(huì)置為無(wú)效,假如不讀就不會(huì)置為無(wú)效然后重新獲取

還是上面的栗子,不過多了一個(gè)線程B,線程B一開始讀的是默認(rèn)值,后來(lái)再進(jìn)行了一次讀取

在這里插入圖片描述

總結(jié)一下讀寫語(yǔ)義

讀寫語(yǔ)義對(duì)應(yīng)的其實(shí)就是volatile的變量修飾后,會(huì)進(jìn)行怎樣的過程

其實(shí)volatile的讀寫語(yǔ)義,就是線程之間的通信,所以volatile也是實(shí)現(xiàn)了線程之間的通信,來(lái)提供可見性

  • 線程A去寫volatile變量,實(shí)質(zhì)上是線程A對(duì)其他要操控該volatile變量的其他線程發(fā)出了消息,該消息表明了線程A已經(jīng)把該變量修改了,其他線程需要重新去獲取
  • 線程B去讀volatile變量時(shí),實(shí)質(zhì)上是線程B接收到了之前某個(gè)線程發(fā)出的消息(可能沒有消息,不過也認(rèn)為接收到),知道這個(gè)變量改了,需要去重新獲取
  • 所以A寫B(tài)讀,就實(shí)現(xiàn)了兩個(gè)線程之間的通信,雖然不太嚴(yán)謹(jǐn),因?yàn)榭赡蹵不寫,B也要讀

volatile的實(shí)現(xiàn)

前面已經(jīng)提到過volatile的實(shí)現(xiàn),字節(jié)碼上加了acc_volatile修飾符,然后指令層面上是使用了內(nèi)存屏障,下面就來(lái)再詳細(xì)研究

volatile的內(nèi)存語(yǔ)義實(shí)現(xiàn)

volatile還有一個(gè)功能就是可以防止命令重排序,也就是volatile的內(nèi)存語(yǔ)義

為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,JMM會(huì)限制重排序,因?yàn)橹嘏判驎?huì)讓語(yǔ)義出現(xiàn)變化,也就是會(huì)打斷與別的線程的通信,前面提到過,重排序總共有三種,而JMM會(huì)限制編譯器重排序與處理器重排序,并不會(huì)限制內(nèi)存重排序

單純看表,很難去辨別為什么,所以下面只看不發(fā)生重排序的部分

  • 當(dāng)?shù)诙€(gè)操作是volatile寫時(shí),無(wú)論第一個(gè)操作是什么,都不能發(fā)生重排序,保證了volatile寫之前的操作不會(huì)被重排序到寫后面
  • 當(dāng)?shù)谝粋€(gè)操作是volatile讀的時(shí)候,無(wú)論第二個(gè)操作是什么,都不能發(fā)生重排序,保證了volatile讀之后的操作不會(huì)被重排序到讀之前
  • 當(dāng)?shù)谝粋€(gè)操作為volatile寫的時(shí)候,且第二個(gè)操作是volatile讀的時(shí)候,是不可以發(fā)生重排序

第三個(gè)比較容易理解,因?yàn)関olatile寫會(huì)影響后面volatile讀的嘛,先寫后讀跟線讀后寫是完全不一樣的,所以兩次操作分別為volatile讀和volatile寫或volatile寫和volatile讀都是不允許重排序的

關(guān)鍵在于前兩條怎么理解

其實(shí)都是因?yàn)関olatile的讀語(yǔ)義,每次volatile讀都會(huì)使緩存行失效,需要去重新獲取緩存行,緩存行中不僅有volatile變量,還有其他共享變量

現(xiàn)在回到第二條

  • 當(dāng)?shù)谝粋€(gè)操作為volatile讀的時(shí)候,后面也是普通讀,重排序是沒有問題,但如果后面是普通寫,普通寫后續(xù)可能是會(huì)刷新進(jìn)主存中的,此時(shí)volatile讀是會(huì)出現(xiàn)問題的
  • 當(dāng)?shù)谝粋€(gè)操作為volatile讀的時(shí)候,第二個(gè)操作也為volatile讀的時(shí)候,會(huì)形成兩次新的緩存行,而每次緩存行相同變量對(duì)應(yīng)的值都可能不一樣,此時(shí)如果發(fā)生重排序,就會(huì)出現(xiàn)不一致,比如,不發(fā)生重排序時(shí),從第一次新的緩存行里面讀A,從第二次新的緩存行里面讀B,發(fā)生了重排序后,就是從第一次新的緩存行里面讀B2,從第二次新的緩存行里面讀A2,B與B2是不一樣的,A于A2也是不一樣的,所以不可以重排序

現(xiàn)在回到第一條

  • 當(dāng)?shù)谝粋€(gè)操作為volatile寫的時(shí)候,會(huì)直接修改主存,影響后面的volatile讀,所以對(duì)于第二個(gè)操作為volatile讀是不可以重排序的
  • 當(dāng)?shù)谝粋€(gè)操作為volatile寫的時(shí)候,會(huì)直接修改主存,是會(huì)對(duì)其他線程造成影響的,同時(shí)重排序的話,會(huì)造成結(jié)果不一致,所以也不可以重排序volatile寫
  • 當(dāng)?shù)谝粋€(gè)操作為volatile寫的時(shí)候,可以普通讀,但不可以普通寫,因?yàn)槠胀▽懞竺嬉矔?huì)更新到主存中去,重排序也是會(huì)導(dǎo)致結(jié)果不一致的

接下來(lái)關(guān)于不需要重排序

  • 普通讀寫和普通讀寫之前沒有volatile要求,所以可以重排序,當(dāng)然這會(huì)導(dǎo)致并發(fā)問題
  • 普通讀寫和volatile讀之間,只有一個(gè)volatile讀要求,這個(gè)讀要求不會(huì)被普通讀寫影響,所以也是可以重排序,不過對(duì)于普通讀寫部分會(huì)產(chǎn)生并發(fā)問題

為了實(shí)現(xiàn)內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類型的處理器重排序,也就是上面提到的限制重排序的類型,對(duì)于執(zhí)行效率來(lái)說(shuō),屏障數(shù)越少越好,但讓JMM去動(dòng)態(tài)發(fā)現(xiàn)最優(yōu)的屏障布置是不可能的,所以采用了保守策略的JMM內(nèi)存屏障和插入策略

1.在每一個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障,保證了在volatile寫操作之前,上面的所有寫操作已經(jīng)執(zhí)行完成,并且都刷新到主存中

2.在每一個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障,保證了必須執(zhí)行完volatile寫操作,下面的讀操作才可以執(zhí)行

3.在每一個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障,保證了在volatile讀之前,上面的所有讀操作都要完成

4.在每一個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障,保證了下面的寫操作,必須要等待volatile讀操作完成才可以繼續(xù)

由于第一次操作為普通讀,第二次操作為volatile讀是允許發(fā)生重排序的,所以volatile讀前面不需要加內(nèi)存屏障

到此這篇關(guān)于深度理解Java中volatile的內(nèi)存語(yǔ)義的文章就介紹到這了,更多相關(guān)volatile的內(nèi)存語(yǔ)義內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot Redis批量存取數(shù)據(jù)的操作

    SpringBoot Redis批量存取數(shù)據(jù)的操作

    這篇文章主要介紹了SpringBoot Redis批量存取數(shù)據(jù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Spring Boot 中常用的注解@RequestParam及基本用法

    Spring Boot 中常用的注解@RequestParam及基本用法

    @RequestParam 是 Spring Framework 和 Spring Boot 中常用的注解之一,用于從請(qǐng)求中獲取參數(shù)值,本文給大家介紹Spring Boot 中常用的注解@RequestParam,感興趣的朋友一起看看吧
    2023-10-10
  • Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它

    Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它

    這篇文章主要介紹了Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它,需要的朋友可以參考下
    2017-02-02
  • 詳解java封裝返回結(jié)果與RestControllerAdvice注解

    詳解java封裝返回結(jié)果與RestControllerAdvice注解

    這篇文章主要為大家介紹了java封裝返回結(jié)果與RestControllerAdvice注解實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • java如何將map數(shù)據(jù)存入到實(shí)體類對(duì)象中

    java如何將map數(shù)據(jù)存入到實(shí)體類對(duì)象中

    在Java編程中,經(jīng)常需要將Map集合中的數(shù)據(jù)轉(zhuǎn)換為實(shí)體類對(duì)象,這可以通過反射機(jī)制實(shí)現(xiàn),即通過遍歷Map對(duì)象,使用反射根據(jù)鍵名對(duì)應(yīng)實(shí)體類的屬性名,動(dòng)態(tài)調(diào)用setter方法將值設(shè)置到實(shí)體對(duì)象中,這樣的操作使得數(shù)據(jù)從Map結(jié)構(gòu)轉(zhuǎn)移到了具體的JavaBean中,便于后續(xù)的操作和管理
    2024-09-09
  • Java8對(duì)List排序的方法大全

    Java8對(duì)List排序的方法大全

    這篇文章主要給大家介紹了關(guān)于Java8對(duì)List排序的方法大全,其實(shí)Java針對(duì)數(shù)組和List的排序都有實(shí)現(xiàn),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • Spring處理@Async導(dǎo)致的循環(huán)依賴失敗問題的方案詳解

    Spring處理@Async導(dǎo)致的循環(huán)依賴失敗問題的方案詳解

    這篇文章主要為大家詳細(xì)介紹了SpringBoot中的@Async導(dǎo)致循環(huán)依賴失敗的原因及其解決方案,文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下
    2022-07-07
  • JVM角度調(diào)試優(yōu)化MyEclipse

    JVM角度調(diào)試優(yōu)化MyEclipse

    這篇文章主要介紹了從JVM角度對(duì)MyEclipse進(jìn)行調(diào)試優(yōu)化,為大家分析調(diào)試優(yōu)化MyEclipse的步驟,感興趣的小伙伴們可以參考一下
    2016-05-05
  • SpringBoot集成阿里云OSS圖片上傳

    SpringBoot集成阿里云OSS圖片上傳

    這篇文章主要為大家詳細(xì)介紹了SpringBoot集成阿里云OSS圖片上傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • Java實(shí)現(xiàn)ECDSA簽名算法

    Java實(shí)現(xiàn)ECDSA簽名算法

    這篇文章主要介紹了Java實(shí)現(xiàn)ECDSA簽名算法,幫助大家更好得利用Java實(shí)現(xiàn)機(jī)器學(xué)習(xí)算法,感興趣的朋友可以了解下
    2020-10-10

最新評(píng)論

淳化县| 石台县| 聂荣县| 东乌珠穆沁旗| 寿光市| 兰考县| 通城县| 庆安县| 扎鲁特旗| 大石桥市| 忻州市| 正定县| 拉孜县| 鄢陵县| 安义县| 沈阳市| 筠连县| 淮南市| 新竹县| 项城市| 灯塔市| 晋宁县| 彭山县| 洪洞县| 青川县| 额济纳旗| 类乌齐县| 惠东县| 莎车县| 西峡县| 靖江市| 麻栗坡县| 武夷山市| 府谷县| 方山县| 桐柏县| 石林| 余庆县| 门源| 桦南县| 夏津县|