亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java并發(fā)編程關(guān)鍵字volatile保證可見(jiàn)性不保證原子性詳解

 更新時(shí)間:2022年02月08日 14:49:14   作者:負(fù)債程序猿  
這篇文章主要為大家介紹了java并發(fā)編程關(guān)鍵字volatile保證可見(jiàn)性不保證原子性詳解,文中附含詳細(xì)示例說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助

volatile關(guān)鍵字可以說(shuō)是Java虛擬機(jī)提供的最輕量級(jí)的同步機(jī)制,但對(duì)于為什么它只能保證可見(jiàn)性,不保證原子性,它又是如何禁用指令重排的,還有很多同學(xué)沒(méi)徹底理解

相信我,堅(jiān)持看完這篇文章,你將牢牢掌握一個(gè)Java核心知識(shí)點(diǎn)

先說(shuō)它的兩個(gè)作用:

保證變量在內(nèi)存中對(duì)線程的可見(jiàn)性禁用指令重排

每個(gè)字都認(rèn)識(shí),湊在一起就麻了

這兩個(gè)作用通常很不容易被我們Java開發(fā)人員正確、完整地理解,以至于許多同學(xué)不能正確地使用volatile

關(guān)于可見(jiàn)性

不多bb,碼來(lái)

public class VolatileTest {
    private static volatile int count = 0;
    private static void increase() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    increase();
                }
            }).start();
        }
		// 所有線程累加完成后輸出
        while (Thread.activeCount() > 2) Thread.yield();
        System.out.println(count);
    }
}

代碼很好理解,開了十個(gè)線程對(duì)同一個(gè)共享變量count做累加,每個(gè)線程累加1w次

count我們已經(jīng)用volatile修飾,已經(jīng)保證了count對(duì)十個(gè)線程在內(nèi)存中的可見(jiàn)性,按理說(shuō)十個(gè)線程執(zhí)行完畢count的值應(yīng)該10w

然鵝,運(yùn)行多次,結(jié)果都遠(yuǎn)小于期望值

在這里插入圖片描述

是哪個(gè)環(huán)節(jié)出了問(wèn)題?

在這里插入圖片描述

你肯定聽過(guò)一句話:volatile只保證可見(jiàn)性,不保證原子性

這句話就是答案,但是依舊很多人沒(méi)搞懂其中的奧秘

說(shuō)來(lái)話長(zhǎng)我長(zhǎng)話短說(shuō),簡(jiǎn)單來(lái)講就是 count++這個(gè)操作不是原子的,它是分三步進(jìn)行

  • 從內(nèi)存讀取 count 的值
  • 執(zhí)行 count + 1
  • 將 count 的新值寫回

要徹底搞懂這個(gè)問(wèn)題,我們得從字節(jié)碼入手

下面是increase方法編譯后的字節(jié)碼

在這里插入圖片描述

看不懂沒(méi)關(guān)系,我們一行一行來(lái)看:

  • GETSTATIC:讀取 count 的當(dāng)前值
  • ICONST_1:將常量 1 加載到棧頂
  • IADD:執(zhí)行+1
  • PUTSTATIC:寫入count最新值

ICONST_1和IADD其實(shí)就是真正的++操作

關(guān)鍵點(diǎn)來(lái)了,volatile只能保證線程在GETSTATIC這一步拿到的值是最新的,但當(dāng)該線程執(zhí)行到下面幾行指令時(shí),這期間可能就有其它線程把count的值修改了,最終導(dǎo)致舊值把真正的新值覆蓋

懂我意思嗎

所以,并發(fā)編程中,只靠volatile修飾共享變量是不可靠的,最終還是要通過(guò)對(duì)關(guān)鍵方法加鎖來(lái)保證線程安全

就如上面的demo,稍加修改就能實(shí)現(xiàn)真正的線程安全

最簡(jiǎn)單的,給increase方法加個(gè)synchronized (synchronized怎么實(shí)現(xiàn)線程安全的我就不啰嗦了,我以前講過(guò) synchronized底層實(shí)現(xiàn)原理

    private synchronized static void increase() {
        ++count;
    }

run幾下

在這里插入圖片描述

這不就妥了嘛

到現(xiàn)在,對(duì)于以下兩點(diǎn)你應(yīng)該有了新的認(rèn)知

volatile保證變量在內(nèi)存中對(duì)線程的可見(jiàn)性

volatile只保證可見(jiàn)性,不保證原子性

關(guān)于指令重排

并發(fā)編程中,cpu自身和虛擬機(jī)為了提高執(zhí)行效率,都會(huì)采用指令重排(在保證不影響結(jié)果的前提下,將某些代碼亂序執(zhí)行)

  • 關(guān)于cpu:為了從分利用cpu,實(shí)際執(zhí)行指令時(shí)會(huì)做優(yōu)化;
  • 關(guān)于虛擬機(jī):在HotSpot vm中,為了提升執(zhí)行效率,JIT(即時(shí)編譯)模式也會(huì)做指令優(yōu)化

指令重排在大部分場(chǎng)景下確實(shí)能提升執(zhí)行效率,但有些場(chǎng)景對(duì)代碼執(zhí)行順序是強(qiáng)依賴的,此時(shí)我們需要禁用指令重排,如下面這個(gè)場(chǎng)景

在這里插入圖片描述

偽代碼取自《深入理解Java虛擬機(jī)》:

其描述的場(chǎng)景是開發(fā)中常見(jiàn)配置讀取過(guò)程,只是我們?cè)谔幚砼渲梦募r(shí)一般不會(huì)出現(xiàn)并發(fā),所以沒(méi)有察覺(jué)這會(huì)有問(wèn)題。
試想一下,如果定義initialized變量時(shí)沒(méi)有使用volatile修飾,就可能會(huì)由于指令重排序的優(yōu)化,導(dǎo)致位于線程A中最后一條代碼“initialized=true”被提前執(zhí)行(這里雖然使用Java作為偽代碼,但所指的重排序優(yōu)化是機(jī)器級(jí)的優(yōu)化操作,提前執(zhí)行是指這條語(yǔ)句對(duì)應(yīng)的匯編代碼被提前執(zhí)行),這樣在線程B中使用配置信息的代碼就可能出現(xiàn)錯(cuò)誤,而volatile通過(guò)禁止指令重排則可以避免此類情況發(fā)生

禁用指令重排只需要將變量聲明為volatile,是不是很神奇

我們來(lái)看看volatile是如何實(shí)現(xiàn)禁用指令重排的

也借用《深入理解Java虛擬機(jī)》的一個(gè)例子吧,比較好理解

在這里插入圖片描述

這是個(gè)單例模式的實(shí)現(xiàn),下面是它的部分字節(jié)碼,紅框中 mov%eax,0x150(%esi) 是對(duì)instance賦值

在這里插入圖片描述

可以看到,在賦值后,還執(zhí)行了 lock addl$0x0,(%esp) 指令,關(guān)鍵點(diǎn)就在這兒,這行指令相當(dāng)于此處設(shè)置了個(gè) 內(nèi)存屏障 ,有了內(nèi)存屏障后,cpu或虛擬機(jī)在指令重排時(shí)就不能把內(nèi)存屏障后面的指令提前到內(nèi)存屏障前面,好好捋一下這段話

最后,留一個(gè)能加深大家對(duì)volatile理解的問(wèn)題,兄弟們好好思考下:

Java代碼明明是從上往下依次執(zhí)行,為什么會(huì)出現(xiàn)指令重排這個(gè)問(wèn)題?

以上就是java并發(fā)編程關(guān)鍵字volatile保證可見(jiàn)性不保證原子性詳解的詳細(xì)內(nèi)容,更多關(guān)于java并發(fā)編程關(guān)鍵字volatile的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用Spring事物時(shí)不生效的場(chǎng)景及解決方法

    使用Spring事物時(shí)不生效的場(chǎng)景及解決方法

    今天介紹一下Spring事物不生效的場(chǎng)景,事物是我們?cè)陧?xiàng)目中經(jīng)常使用的,如果是Java的話,基本上都使用Spring的事物,不過(guò)Spring的事物如果使用不當(dāng),那么就會(huì)導(dǎo)致事物失效或者不回滾,最終導(dǎo)致數(shù)據(jù)不一致,下面我們意義列舉不生效的場(chǎng)景,并給出解決方法
    2023-09-09
  • Java實(shí)現(xiàn)FIFO、LRU、LFU、OPT頁(yè)面置換算法

    Java實(shí)現(xiàn)FIFO、LRU、LFU、OPT頁(yè)面置換算法

    本文主要介紹了Java實(shí)現(xiàn)FIFO、LRU、LFU、OPT頁(yè)面置換算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • 利用Java編寫一個(gè)出敬業(yè)福的小程序

    利用Java編寫一個(gè)出敬業(yè)福的小程序

    新年將至,又開始掃?;顒?dòng),每年的敬業(yè)福成了大家難過(guò)的坎。所以本文將介紹一個(gè)通過(guò)Java編寫的一款福字生成器,感興趣的小伙伴可以試一試
    2022-01-01
  • Mybatis使用MySQL模糊查詢時(shí)輸入中文檢索不到結(jié)果怎么辦

    Mybatis使用MySQL模糊查詢時(shí)輸入中文檢索不到結(jié)果怎么辦

    這篇文章主要介紹了Mybatis使用MySQL模糊查詢時(shí)輸入中文檢索不到結(jié)果的解決辦法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-07-07
  • Tomcat 服務(wù)器 在45秒內(nèi)未啟動(dòng)成功的解決方法

    Tomcat 服務(wù)器 在45秒內(nèi)未啟動(dòng)成功的解決方法

    下面小編就為大家?guī)?lái)一篇Tomcat 服務(wù)器 在45秒內(nèi)未啟動(dòng)成功的解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-11-11
  • 簡(jiǎn)單談?wù)凴xJava和多線程并發(fā)

    簡(jiǎn)單談?wù)凴xJava和多線程并發(fā)

    認(rèn)識(shí)RxJava已經(jīng)有一段時(shí)間了,但是一直沒(méi)有機(jī)會(huì)在項(xiàng)目中嘗試,最近在新的項(xiàng)目里引進(jìn)了RxJava寫一些事件處理,在review代碼的時(shí)候發(fā)現(xiàn)了一些和多線程并發(fā)相關(guān)的問(wèn)題,所以寫了這篇文章,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-03-03
  • Java String 對(duì)象(你真的了解了嗎)

    Java String 對(duì)象(你真的了解了嗎)

    這篇文章主要介紹了Java String 對(duì)象(你真的了解了嗎),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 詳解java WebSocket的實(shí)現(xiàn)以及Spring WebSocket

    詳解java WebSocket的實(shí)現(xiàn)以及Spring WebSocket

    這篇文章主要介紹了詳解java WebSocket的實(shí)現(xiàn)以及Spring WebSocket ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-01-01
  • Mybatis-Plus的saveOrUpdateBatch(null)問(wèn)題及解決

    Mybatis-Plus的saveOrUpdateBatch(null)問(wèn)題及解決

    這篇文章主要介紹了Mybatis-Plus的saveOrUpdateBatch(null)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java8新特性之Stream使用詳解

    Java8新特性之Stream使用詳解

    這篇文章主要介紹了Java8新特性之Stream使用詳解,流是用來(lái)處理集合中的數(shù)據(jù),以聲明的形式操作集合,它就像SQL語(yǔ)句,我們只需告訴流需要對(duì)集合進(jìn)行什么操作,它就會(huì)自動(dòng)進(jìn)行操作,并將執(zhí)行結(jié)果交給你,無(wú)需我們自己手寫代碼,需要的朋友可以參考下
    2023-08-08

最新評(píng)論