Java內(nèi)存模型(四)volatile的內(nèi)存語(yǔ)義

      網(wǎng)友投稿 644 2025-04-01

      1、Volatile的特性

      理解Volatile特性的一個(gè)好辦法是把對(duì)volatile變量的單個(gè)讀/寫(xiě),看成是使用同一個(gè)鎖對(duì)單個(gè)讀/寫(xiě)操作做了同步。

      代碼示例:

      package com.lizba.p1; /** *

      * volatile示例 *

      * * @Author: Liziba * @Date: 2021/6/9 21:34 */ public class VolatileFeatureExample { /** 使用volatile聲明64位的long型變量 */ volatile long v1 = 0l; /** * 單個(gè)volatile寫(xiě)操作 * @param l */ public void set(long l) { v1 = l; } /** * 復(fù)合(多個(gè))volatile讀&寫(xiě) */ public void getAndIncrement() { v1++; } /** * 單個(gè)volatile變量的讀 * @return */ public long get() { return v1; } }

      假設(shè)有多個(gè)線程分別調(diào)用上面程序的3個(gè)方法,這個(gè)程序在語(yǔ)義上和下面程序等價(jià)。

      package com.lizba.p1; /** *

      * synchronized等價(jià)示例 *

      * * @Author: Liziba * @Date: 2021/6/9 21:46 */ public class SynFeatureExample { /** 定義一個(gè)64位長(zhǎng)度的普通變量 */ long v1 = 0L; /** * 使用同步鎖對(duì)v1變量進(jìn)行寫(xiě)操作 * @param l */ public synchronized void set(long l) { v1 = l; } /** * 通過(guò)同步讀和同步寫(xiě)方法對(duì)v1進(jìn)行+1操作 */ public void getAndIncrement() { long temp = get(); // v1加一 temp += 1L; set(temp); } /** * 使用同步鎖對(duì)v1進(jìn)行讀操作 * @return */ public synchronized long get() { return v1; } }

      如上兩個(gè)程序所示,一個(gè)volatile變量的單個(gè)讀\寫(xiě)操作,與一個(gè)普通變量的讀\寫(xiě)操作都是使用同一個(gè)鎖來(lái)同步,它們之間的執(zhí)行效果相同。

      上述代碼總結(jié):

      鎖的happens-before規(guī)則保證釋放鎖和獲取鎖的兩個(gè)線程之間的內(nèi)存可見(jiàn)性,這意味著對(duì)一個(gè)volatile變量的讀,總能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入。

      鎖的語(yǔ)義決定了臨界區(qū)代碼的執(zhí)行具有原子性。這意味著,即使是64位的long型和double型變量,只要它是volatile變量,對(duì)該變量的讀/寫(xiě)就具有原子性。如果是多個(gè)volatile操作或類似于volatile++這種復(fù)合操作,這些操作整體上不具備原子性。

      總結(jié)volatile特性:

      可見(jiàn)性。對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入。

      原子性。對(duì)任意volatile變量的讀/寫(xiě)具有原子性,但類似volatile++這種復(fù)合操作不具有原子性。

      2、volatile寫(xiě)-讀建立的happens-before關(guān)系

      對(duì)于程序員來(lái)說(shuō),我們更加需要關(guān)注的是volatile對(duì)線程內(nèi)存的可見(jiàn)性。

      從JDK1.5(JSR-133)開(kāi)始,volatile變量的寫(xiě)-讀可以實(shí)現(xiàn)線程之間的通信。從內(nèi)存語(yǔ)義的角度來(lái)說(shuō),volatile的寫(xiě)-讀與鎖的釋放-獲取有相同的內(nèi)存效果。

      volatile的寫(xiě)和鎖的釋放有相同的內(nèi)存語(yǔ)義

      volatile的讀和鎖的獲取有相同的內(nèi)存語(yǔ)義

      代碼示例:

      package com.lizba.p1; /** *

      * *

      * * @Author: Liziba * @Date: 2021/6/9 22:23 */ public class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; // 1 flag = true; // 2 } public void reader() { if (flag) { // 3 int i = a; // 4 System.out.println(i); } } }

      假設(shè)線程A執(zhí)行writer()方法之后,線程B執(zhí)行reader()方法。根據(jù)happens-before規(guī)則,這個(gè)過(guò)程建立的happens-before關(guān)系如下:

      根據(jù)程序次序規(guī)則,1 happens-before 2, 3 happens-before 4。

      根據(jù)volatile規(guī)則,2 happens-before 3。

      根據(jù)happens-before的傳遞性規(guī)則,1 happens-before 4。

      圖示上述happens-before關(guān)系:

      總結(jié):這里A線程寫(xiě)一個(gè)volatile變量后,B線程讀同一個(gè)volatile變量。A線程在寫(xiě)volatile變量之前所有可見(jiàn)的共享變量,在B線程讀同一個(gè)volatile變量后,將立即對(duì)B線程可見(jiàn)。

      3、volatile寫(xiě)-讀的內(nèi)存語(yǔ)義

      當(dāng)寫(xiě)一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

      以上面的VolatileExample為例,假設(shè)A線程首先執(zhí)行writer()方法,隨后線程B執(zhí)行reader()方法,初始時(shí)兩個(gè)線程的本地內(nèi)存中的flag和a都是初始狀態(tài)。

      A執(zhí)行volatile寫(xiě)后,共享變量狀態(tài)示意圖。

      線程A在寫(xiě)flag變量后,本地內(nèi)存A中被線程A更新過(guò)的兩個(gè)共享變量的值被刷新到主內(nèi)存中,此時(shí)A的本地內(nèi)存和主內(nèi)存中的值是一致的。

      當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。線程接下來(lái)將會(huì)從主內(nèi)存中讀取共享變量。

      B執(zhí)行volatile讀后,共享變量的狀態(tài)示意圖。

      在讀flag變量后,本地內(nèi)存B包含的值已經(jīng)被置為無(wú)效。此時(shí),線程B必須從主內(nèi)存中重新讀取共享變量。線程B的讀取操作將導(dǎo)致本地內(nèi)存B與主內(nèi)存中的共享變量的值變?yōu)橐恢隆?/p>

      總結(jié)volatile的寫(xiě)和volatile讀的內(nèi)存語(yǔ)義

      線程A寫(xiě)一個(gè)volatile變量,實(shí)質(zhì)上是線程A向接下來(lái)將要讀這個(gè)volatile變量的某個(gè)線程發(fā)出了(其對(duì)共享變量所做修改的)消息。

      線程B讀一個(gè)volatile變量,實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在寫(xiě)這個(gè)volatile變量之前對(duì)共享變量所做修改的)消息。

      線程A寫(xiě)一個(gè)volatile變量,隨后線程B讀這個(gè)volatile變量,這個(gè)過(guò)程實(shí)質(zhì)上是線程A通過(guò)主內(nèi)存向線程B發(fā)送消息。

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

      程序的重排序分為編譯器重排序和處理器重排序(我的前面的博文內(nèi)容有寫(xiě)哈)。為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,JMM會(huì)分別禁止這兩種類型的重排序。

      volatile重排序規(guī)則表

      是否能重排序

      第二個(gè)操作

      Java內(nèi)存模型(四)volatile的內(nèi)存語(yǔ)義

      第一個(gè)操作

      普通讀/寫(xiě)

      volatile讀

      volatile寫(xiě)

      普通讀/寫(xiě)

      NO

      volatile讀

      NO

      NO

      NO

      volatile寫(xiě)

      NO

      NO

      上圖舉例:第一行最后一個(gè)單元格意思是,在程序中第一個(gè)操作為普通讀/寫(xiě)時(shí),如果第二個(gè)操作為volatile寫(xiě),則編譯器不能重排序。

      總結(jié)上圖:

      第二個(gè)操作是volatile寫(xiě)時(shí),都不能重排序。確保volatile寫(xiě)之前的操作不會(huì)被編譯器重排序到volatile之后

      第一個(gè)操作為volatile讀時(shí),都不能重排序。確保volatile讀之后的操作不會(huì)被編譯器重排序到volatile之前

      第一個(gè)操作為volatile寫(xiě),第二個(gè)操作為volatile讀時(shí),不能重排序。

      為了實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類型的處理器重排序。

      JMM采取的是保守策略內(nèi)存屏障插入策略,如下:

      在每個(gè)volatile寫(xiě)操作屏障前面插入一個(gè)StoreStore屏障。

      在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障

      在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。

      在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。

      保守策略可以保證在任意處理器平臺(tái)上,任意程序中都能得到正確的volatile內(nèi)存語(yǔ)義。

      保守策略下,volatile寫(xiě)插入內(nèi)存屏障后生成的指令序列圖:

      解釋:

      StoreStore屏障可以保證在volatile寫(xiě)之前,其前面所有普通寫(xiě)操作已經(jīng)對(duì)任意處理器可見(jiàn)了。這是因?yàn)镾toreStore屏障將保障上面所有普通寫(xiě)在volatile寫(xiě)之前刷新到主內(nèi)存。

      保守策略下,volatile讀插入內(nèi)存屏障后生成的指令序列圖:

      解釋:

      LoadLoad屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通寫(xiě)重排序。

      上述volatile寫(xiě)和volatile讀的內(nèi)存屏障插入策略非常保守。在實(shí)際執(zhí)行時(shí),只要不改變volatile寫(xiě)-讀的內(nèi)存語(yǔ)義,編譯器可以根據(jù)具體情況省略不必要的屏障。

      代碼示例:

      package com.lizba.p1; /** *

      * volatile屏障示例 *

      * * @Author: Liziba * @Date: 2021/6/9 23:48 */ public class VolatileBarrierExample { int a; volatile int v1 = 1; volatile int v2 = 2; void readAndWrite() { // 第一個(gè)volatile讀 int i = v1; // 第二個(gè)volatile讀 int j = v2; // 普通寫(xiě) a = i + j; // 第一個(gè)volatile寫(xiě) v1 = i + 1; // 第二個(gè)volatile寫(xiě) v2 = j * 2; } // ... 其他方法 }

      針對(duì)VolatileBarrierExample的readAndWrite(),編譯器生成字節(jié)碼時(shí)可以做如下優(yōu)化:

      注意:最后的StoreLoad屏障無(wú)法省略。因?yàn)榈诙€(gè)volatile寫(xiě)之后,程序return。此時(shí)編譯器無(wú)法準(zhǔn)確斷定后面是否會(huì)有volatile讀寫(xiě)操作,為了安全起見(jiàn),編譯器通常會(huì)在這里插入一個(gè)StoreLoad屏障。

      上面的優(yōu)化可以針對(duì)任意處理器平臺(tái),但是由于不同的處理器有不同的“松緊度”的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化。

      X86處理器平臺(tái)優(yōu)化

      X86處理器僅會(huì)對(duì)寫(xiě)-讀操作做重排序。X86不會(huì)對(duì)讀-讀、讀-寫(xiě)和寫(xiě)-寫(xiě)重排序,因此X86處理器會(huì)省略掉這3種操作類型對(duì)應(yīng)的內(nèi)存屏障。在X86平臺(tái)中,JMM僅需要在volatile寫(xiě)后插入一個(gè)StoreLoad屏障即可正確實(shí)現(xiàn)volatile寫(xiě)-讀內(nèi)存語(yǔ)義。同時(shí)這樣意味著X86處理器中,volatile寫(xiě)的開(kāi)銷會(huì)遠(yuǎn)遠(yuǎn)大于讀的開(kāi)銷。

      5、volatile和鎖的比較

      功能上:

      鎖比volatile更強(qiáng)大

      可伸縮性和執(zhí)行性能上:

      volatile更具有優(yōu)勢(shì)

      文章總結(jié)至《Java并發(fā)編程藝術(shù)》,下篇總結(jié)“鎖的內(nèi)存語(yǔ)義”,敬請(qǐng)關(guān)注。

      Java

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:告訴你個(gè)秘密:斯皮爾伯格的《頭號(hào)玩家》其實(shí)不是科幻片!
      下一篇:wps如何制作圖表(wps怎么圖表制作)
      相關(guān)文章
      亚洲无成人网77777| 国产天堂亚洲国产碰碰| 亚洲AV噜噜一区二区三区| 婷婷久久久亚洲欧洲日产国码AV| JLZZJLZZ亚洲乱熟无码| 亚洲AV女人18毛片水真多| 亚洲精品国产suv一区88| 亚洲成亚洲乱码一二三四区软件| 亚洲a∨国产av综合av下载| 亚洲精品不卡视频| 国产V亚洲V天堂无码久久久| 亚洲综合av一区二区三区| 中文字幕精品亚洲无线码一区应用| 亚洲精品一卡2卡3卡四卡乱码| 亚洲色大成网站www久久九| 国产亚洲中文日本不卡二区| 亚洲一区影音先锋色资源| 亚洲黄色在线视频| 亚洲欧洲自拍拍偷午夜色| 亚洲区视频在线观看| 亚洲视频一区在线播放| 亚洲网址在线观看| 亚洲制服丝袜第一页| 亚洲欧美aⅴ在线资源| 亚洲av成人片在线观看| 久久综合亚洲色hezyo| 亚洲精品无码你懂的| 亚洲?V无码成人精品区日韩 | 亚洲中文字幕在线观看| 亚洲综合av永久无码精品一区二区| 亚洲午夜福利AV一区二区无码| 国产AV无码专区亚洲AVJULIA| 亚洲AV日韩精品久久久久久 | 亚洲五月丁香综合视频| 亚洲免费福利视频| 久久亚洲国产精品成人AV秋霞| 亚洲国产超清无码专区| 国产亚洲精品成人AA片| 亚洲AV无码AV吞精久久| 亚洲av无码成人精品区在线播放 | 亚洲人成网站在线在线观看|