日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JMM中的原子性、可见性、有序性和volatile关键字

發(fā)布時間:2025/4/16 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JMM中的原子性、可见性、有序性和volatile关键字 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

相信如果對JMM底層有過了解或者接觸過java并發(fā)編程的讀者對以上的概念并不陌生,但是真正理解的可能并不多。這里我就對這些概念再做一次講解。相信讀者多讀幾遍應(yīng)該就有自己的理解,實在不理解也沒關(guān)系,說明知識的儲備還不夠,不妨以后再來讀一遍,可能會瞬間突然明白。

參考內(nèi)容:

  • https://mp.weixin.qq.com/s/kQ498ifh4OUEDd829JIhnQ 作者:CodeSheep;他同時也是B站up主,ID就是CodeSheep,視頻良心,有興趣的讀者可以自己查看。
  • 《實戰(zhàn)Java高并發(fā)程序設(shè)計》 作者:葛一鳴 郭超 ;如果是并發(fā)編程初學(xué)者我也推薦這本書,通俗易懂
  • JMM的關(guān)鍵技術(shù)點都是圍繞著多線程的原子性、可見性和有序性來建立的。要理解JMM首先就需要了解這三個特性。而volatile關(guān)鍵字很好的貫徹了可見性和有序性,提到volatile關(guān)鍵字也是為了加深對這三個特性的理解,同時volatile也是非常重要的內(nèi)容,也是難點。

    1、原子性

    原子性其實非常好理解,原子性操作就是指這些操作是不可中斷的,要做一定做完,要么就沒有執(zhí)行,也就是不可被中斷。

    我們使用的int類型的數(shù)據(jù)如果只是是簡單的讀取和賦值的話就是原子操作。下面給出幾個例子:

    i = 2; 賦值給i -----------------------------操作步驟:1 原子操作 j = i; 讀取i值 賦值給j -----------------------操作步驟:2 非原子操作 i++; 讀取i值 i值加1 賦值給i ------------------操作步驟:3 非原子操作 i = i+1; 讀取i值 i值加1 賦值給i --------------操作步驟:3 非原子操作

    但是如果我們不使用int型而使用long型的話,對于32位系統(tǒng)來說,long型數(shù)據(jù)的讀寫不是原子性的(因為long有64位)。虛擬機(jī)規(guī)范中允許對 64位數(shù)據(jù)類型( long和 double),分為 2次 32為的操作來處理,也就是說,如果兩個線程同時對long進(jìn)行寫入的話(或者讀取),對線程之間的結(jié)果是有干擾的。可能高位是一個線程寫的,低位又是另一個線程寫的,如果這時候讀的話,就會讀到錯誤的值。不是線程1寫的值,也不是線程2寫的值。但是最新 JDK實現(xiàn)還是實現(xiàn)了原子操作的。JMM只實現(xiàn)了基本的原子性,像上面 i++那樣的操作,必須借助于 synchronized和 Lock來保證整塊代碼的原子性了。

    2、可見性

    可見性是指當(dāng)一個線程修改了某一個共享變量的值,其他線程是否能夠立即知道這個修改。

    顯然,對于串行程序來說,可見性問題是不存在的。因為你在任何一個操作步驟中修改了某個變量,那么在后續(xù)的步驟中,讀取這個變量的值,一定是修改后的新值。但是這個問題在并行程序中就不見得了。如果一個線程修改了某一個全局變量,那么其他線程未必可以馬上知道這個改動。這個問題可能由cache優(yōu)化引起,比如下面這個例子:

    如果在CPU1和CPU2上各運(yùn)行了一個線程,它們共享變量t,由于編譯器優(yōu)化或者硬件優(yōu)化的緣故,在CPU1上的線程將變量t進(jìn)行了優(yōu)化,將其緩存在cache中或者寄存器里。這種情況下,如果在CPU2上的某個線程修改了變量t的實際值,那么CPU1上的線程可能并無法意識到這個改動,依然會讀取cache中或者寄存器里的數(shù)據(jù)。因此,就產(chǎn)生了可見性問題。外在表現(xiàn)為:變量t的值被修改,但是CPU1上的線程依然會讀到一個舊值。可見性問題也是并行程序開發(fā)中需要重點關(guān)注的問題之一。

    可見性問題是一個綜合性問題。除了上述提到的緩存優(yōu)化或者硬件優(yōu)化(有些內(nèi)存讀寫可能不會立即觸發(fā),而會先進(jìn)入一個硬件隊列等待)會導(dǎo)致可見性問題外,指令重排(這個問題將在下一節(jié)中更詳細(xì)討論)以及編輯器的優(yōu)化,都有可能導(dǎo)致一個線程的修改不會立即被其他線程察覺。

    3、有序性

    JMM是允許編譯器和處理器對指令重排序的,但是規(guī)定了 as-if-serial語義,即不管怎么重排序,程序的執(zhí)行結(jié)果不能改變。比如下面的程序段:

    double pi = 3.14; // A double r = 1; // B doubles = pi *r *r; // C

    無論是 A->B->C 還是 B->A->C 都對結(jié)果沒有影響。但是這是發(fā)生在單線程之中的。在多線程之中可能就不是這樣了,多線程有序性引起的問題我們可以看一個典型的例子:

    class OrderExample{int a = 0;boolean flag = false;public void writer(){a = 1;flag = true;}public void reader(){if(flag){int i = a + 1;}} }

    如果這個類的writer()和reader()方法是在不同的線程中運(yùn)行的。那么writer()中的方法可能會被重排序為flag= true先執(zhí)行。這個時候如果被中斷,換到執(zhí)行reader()的線程執(zhí)行,flag為true,進(jìn)入if判斷就會自然認(rèn)為a = 1;但是這個時候a還是0。這里大概就能理解重排序帶來的問題了。

    JMM具備一些先天的有序性,即不需要通過任何手段就可以保證的有序性,也就是在下面這些情況中,是不能進(jìn)行重排序的。通常稱為 happens-before原則

  • 程序順序規(guī)則:一個線程中的每個操作,happens-before于該線程中的任意后續(xù)操作,但是在 JMM里其實只要執(zhí)行結(jié)果一樣,是允許重排序的,這邊的 happens-before強(qiáng)調(diào)的重點也是單線程執(zhí)行結(jié)果的正確性,但是無法保證多線程也是如此。上面的例子已經(jīng)很好的說明了這一點。
  • 監(jiān)視器鎖規(guī)則:對一個線程的解鎖,happens-before于隨后對這個線程的加鎖
  • volatile變量規(guī)則:對一個volatile域的寫,happens-before于后續(xù)對這個volatile域的讀。也就是一個線程寫了volatile域,其他線程如果執(zhí)行這個域的讀操作就會知道它改變了。注意這里必須是另一個線程執(zhí)行讀操作才能知道。具體可以看下面對volatile的講解。
  • 傳遞性:如果A happens-before B ,且 B happens-before C, 那么 A happens-before C
  • start()規(guī)則:如果線程A執(zhí)行操作ThreadBstart()(啟動線程B) , 那么A線程的ThreadBstart()happens-before 于B中的任意操作
  • join()原則:如果A執(zhí)行ThreadB.join()并且成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
  • interrupt()原則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程代碼檢測到中斷事件的發(fā)生,可以通Thread.interrupted()方法檢測是否有中斷發(fā)生
  • finalize()原則:一個對象的初始化完成先行發(fā)生于它的finalize()方法的開始
  • 關(guān)于為什么需要重排序,這里再詳細(xì)說明一下:
    比如執(zhí)行:

    a = b + c; d = e + f;

    在cpu中執(zhí)行的過程可能是這樣:

    左邊是匯編指令,右邊就是流水線的情況。注意,在ADD指令上,有一個大叉,表示一個中斷。也就是說ADD在這里停頓了一下。為什么ADD會在這里停頓呢?原因很簡單,R2中的數(shù)據(jù)還沒有準(zhǔn)備好!所以,ADD操作必須進(jìn)行一次等待。由于ADD的延遲,導(dǎo)致其后面所有的指令都要慢一個節(jié)拍。

    既然停頓是因為數(shù)據(jù)還沒有準(zhǔn)備好,那我們就在它等待數(shù)據(jù)準(zhǔn)備好的時候做其他事情。也就是在ADD和前面的LW指令之間插入一個做其他事情的指令,SUB同理,具體來說我們可以這樣移動指令:

    變成:

    可以看到一共節(jié)約了兩步執(zhí)行時間。

    4、volatile關(guān)鍵字

    被 volatile修飾的共享變量,具有以下兩點特性:

  • 保證了不同線程對該變量操作的內(nèi)存可見性;
  • 禁止指令重排序
  • JMM規(guī)定對一個 volatile域的寫, happens-before于后續(xù)對這個 volatile域的讀(也就是一個線程寫了volatile域,其他線程如果執(zhí)行讀操作就會知道它改變了),其實就是如果一個變量聲明成是 volatile的,那么當(dāng)我讀變量時,總是能讀到它的最新值,這里最新值是指不管其它哪個線程對該變量做了寫操作,都會立刻被更新到主存里,我也能從主存里讀到這個剛寫入的值。

    從內(nèi)存語義上來看
    當(dāng)寫一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存
    當(dāng)讀一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效,線程接下來將從主內(nèi)存中讀取共享變量。

    關(guān)于禁止重排序也就是不會讓volatile寫之前執(zhí)行的結(jié)果跑到后面去,再拿這個例子說明就是a=1;不會到flag = true之后執(zhí)行。

    class OrderExample{int a = 0;boolean flag = false;public void writer(){a = 1;flag = true;}public void reader(){if(flag){int i = a + 1;}} }

    同時保證volatile讀之后的操作不會到volatile讀之前操作;

    但是volatile是無法保證原子性的,但是和int類型變量一樣,簡單的讀取賦值還是原子的,但是這和volatile關(guān)鍵字沒什么關(guān)系,只是作為普通變量的特性。舉個例子,比如線程A讀取了volatile變量,阻塞了。換到線程B讀取了volatile變量執(zhí)行加1操作,寫回。現(xiàn)在又切換到線程A,因為線程A已經(jīng)執(zhí)行了讀操作,無法觸發(fā)線程A感知volatile變量已經(jīng)改變,只有在做讀取操作時,發(fā)現(xiàn)自己緩存行無效,才會去讀主存的值,所以該線程直接加1,寫回。所以雖然執(zhí)行了2次加1,但實際只加了一次加1。理解只有volatile變量的讀操作才能觸發(fā)線程感知變量已經(jīng)改變是非常重要的。

    關(guān)于volatile的底層實現(xiàn)機(jī)制:

    如果把加入 volatile關(guān)鍵字的代碼和未加入 volatile關(guān)鍵字的代碼都生成匯編代碼,會發(fā)現(xiàn)加入 volatile關(guān)鍵字的代碼會多出一個 lock前綴指令。

    lock前綴指令實際相當(dāng)于一個內(nèi)存屏障,內(nèi)存屏障提供了以下功能:

    1 . 重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置
    2 . 使得本CPU的Cache寫入內(nèi)存
    3 . 寫入動作也會引起別的CPU或者別的內(nèi)核無效化其Cache,相當(dāng)于讓新寫入的值對別的線程可見。

    關(guān)于volatile的使用場景

  • 狀態(tài)量標(biāo)記,就如上面對 flag的標(biāo)記,還是這個例子:
  • class OrderExample{int a = 0;volatile boolean flag = false;public void writer(){a = 1;flag = true;}public void reader(){if(flag){int i = a + 1;}} }

    使用volatile來標(biāo)示flag,就能解決上面說到的可見性問題,這種對變量的讀寫操作,標(biāo)記為 volatile可以保證修改對線程立刻可見。比 synchronized, Lock有一定的效率提升。

  • 單例模式的實現(xiàn),典型的雙重檢查鎖定(DCL)
  • class Singleton{private volatile static Singleton instance = null;private Singleton(){}public static Singleton getInstance(){if(instance == null){synchronized(Singleton.class){if(instance == null){instance = new Singleton();}}}return instance;} }

    這是一種懶漢的單例模式,使用時才創(chuàng)建對象,為了避免初始化操作的指令重排序,給 instance加上了 volatile。

    總結(jié)

    以上是生活随笔為你收集整理的JMM中的原子性、可见性、有序性和volatile关键字的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 成人激情开心网 | 国产熟妇搡bbbb搡bbbb搡 | 成年网站 | 最近2018年手机中文字幕版 | 欧洲性猛交 | 亚洲av无码国产精品麻豆天美 | 久久久蜜桃| 亚洲天堂资源 | 日韩电影精品 | 亚洲777 | www三级免费 | 男女av网站| 免费a大片| 足疗店女技师按摩毛片 | 欧美日韩成人一区二区 | 超碰碰碰碰 | 少妇无码av无码专区在线观看 | 欧美 日韩 国产 亚洲 色 | 国产在线欧美日韩 | 国产精品乱码一区二区视频 | 青青草狠狠操 | 风韵丰满熟妇啪啪区老熟熟女 | 亚洲熟女一区二区三区 | 99热这里只有精品5 国产精品伦子伦免费视频 精品一二三 | 日本毛片在线 | 亚州久久久 | 又白又嫩毛又多15p 超碰在线一区 | 国产五月天婷婷 | 国产3页 | 黄色免费在线播放 | 亚洲成a人片77777精品 | 国产日本视频 | 日本一级黄色大片 | 人妖性生活视频 | 天天爱天天射 | 韩国伦理片在线观看 | 黄色片的网站 | 影音先锋啪啪 | 亚洲精品一区二区三区不卡 | 国产午夜精品福利视频 | 日韩天堂在线 | 放荡闺蜜高h苏桃情事h | 欧美性受视频 | 欧洲国产视频 | 国产大片中文字幕 | 亚洲一区欧美日韩 | 美腿丝袜亚洲综合 | 欧美日韩亚洲在线观看 | 亚洲国产激情 | 天天想你在线观看完整版电影高清 | 羞羞答答一区 | 色多多网站 | 在线日本中文字幕 | 麻豆一区二区在线观看 | 麻豆av导航 | 黄色69视频 | 一级黄色免费观看 | 一本一道久久a久久精品综合 | 欧美日韩在线观看一区二区 | 97超碰免费观看 | 国产片91| 麻豆网| 精品久久中文字幕 | 最新日韩一区 | 春日野结衣av| 亚洲视频 中文字幕 | 自拍偷拍另类 | 亚洲成人午夜电影 | 欧美成人午夜免费视在线看片 | 国产精品无码白浆高潮 | 日日爱669 | 久久精品久久久精品美女 | 我们的2018在线观看免费高清 | 欧美日韩一区二区不卡 | caoporn免费在线 | 超级变态重口av番号 | 91porny丨首页入口在线 | 成人在线视频一区 | 成人a级免费视频 | 中文字幕高潮 | 中文字幕在线观看第二页 | 69精品久久久| 林天顾悦瑶笔趣阁 | 亚洲一区二区三区免费观看 | 麻豆黄色片 | 黄色福利在线观看 | 99精品热| 美女中文字幕 | 一级特黄毛片 | 久久视频网 | 亚洲无码精品一区二区三区 | 9久9久9久女女女九九九一九 | 西欧毛片 | 亚洲二区精品 | 黄色福利网 | av在线a| 九九热免费精品视频 | 久久久无码精品亚洲国产 | 黄色在线免费观看视频 |