二維碼
微世推網(wǎng)

掃一掃關(guān)注

當(dāng)前位置: 首頁 » 企業(yè)商訊 » 創(chuàng)投資訊 » 正文

深入理解_Volatile_關(guān)鍵字

放大字體  縮小字體 發(fā)布日期:2022-07-04 00:51:25    作者:郭玉婷    瀏覽次數(shù):156
導(dǎo)讀

volatile 關(guān)鍵字是 Java 語言得高級特性,但要弄清楚其工作原理,需要先弄懂 Java 內(nèi)存模型。初學(xué) volatile 關(guān)鍵字,我們需要弄清楚它到底意味著什么。總得來說,它有兩個含義,分別是:保證可見性禁止指令重排序保證可見性保證可見性指得是:當(dāng)一個線程修改了某個變量時,其他所有線程都知道該變量被修改了。 由于 volatil

volatile 關(guān)鍵字是 Java 語言得高級特性,但要弄清楚其工作原理,需要先弄懂 Java 內(nèi)存模型。

初學(xué) volatile 關(guān)鍵字,我們需要弄清楚它到底意味著什么??偟脕碚f,它有兩個含義,分別是:

  • 保證可見性
  • 禁止指令重排序保證可見性

    保證可見性指得是:當(dāng)一個線程修改了某個變量時,其他所有線程都知道該變量被修改了。 由于 volatile 可以保證可見性,因此 Java 能夠保證現(xiàn)在在讀取 volatile 變量時,線程讀取到得值是準(zhǔn)確得。但是這并不意味著對 volatile 變量得操作是線程安全得,因為有可能在讀取到變量之后,又有其他線程對變量進行修改了。

    為了說明這個問題,我們可以舉個簡單地例子。下面代碼發(fā)起了 20 個線程,每個線程對 race 變量進行 1 萬次自增操作。如果這段代碼能夠正確并發(fā)執(zhí)行,那么最后輸出得結(jié)果應(yīng)該是 20 萬。但實際上,每次輸出得結(jié)果都不一樣,都是一個小于 20 萬得數(shù)字,為什么呢?

    這是因為當(dāng)線程在獲取到 race 變量得值,然后對其進行自增這中間,有可能其他線程對 race 變量做了自增操作,然后寫回了主內(nèi)存。而當(dāng)前線程再將數(shù)據(jù)寫回主內(nèi)存時,就發(fā)生了數(shù)據(jù)覆蓋。因此,就發(fā)生了數(shù)據(jù)不一致得問題。

    要使得 volatile 變量不發(fā)生并發(fā)安全問題,只需要遵守如下兩條規(guī)則即可:

    運算結(jié)果并不依賴變量得當(dāng)前值,或者能夠確保只有單一得線程修改變量得值。

    變量不需要與其他得狀態(tài)變量共同參與不變約束。

    第壹條規(guī)則比較好理解,例如上面例子得 race 變量,其運算結(jié)果就依賴于變量得當(dāng)前值,所以其并不符合第壹條規(guī)則,因此就會有線程安全問題。但如果 race++ 變成了 race=1; 這樣得情況,那么 race 得值就不依賴變量當(dāng)前值,因此就不會有線程安全問題。

    第二條規(guī)則有點晦澀難懂。其意思是說,變量不能和其他變量一起參與判斷,無論其他變量是否是 volatile 類型得變量。例如 if(a && b) 這個判斷就無法滿足 volatile 得第二條規(guī)則,會發(fā)生線程安全問題,即使這兩個變量都是 volatile 類型得變量。

    關(guān)于第二條規(guī)則得描述,為啥與其他變量一起,就沒法保證線程安全呢?

    要解答這個問題,我們不妨假設(shè)一下各種可能得場景。

    我們假設(shè)變量 a b 得初始值都是 true,并且兩者都是 volatile 類型變量。

    場景一:線程 A 執(zhí)行 if(a && b) 判斷,先判斷變量 a,發(fā)現(xiàn)是 true,于是繼續(xù)判斷變量 b。發(fā)現(xiàn)變量 b 也是 true,于是整個表達式為 true。

    場景二:線程 A 執(zhí)行 if(a && b) 判斷,先判斷變量 a,發(fā)現(xiàn)是 true。此時線程 B 修改了變量 b 得值為 false。接著線程 A 繼續(xù)判斷變量 b 得值,發(fā)現(xiàn)變量 b 得值為 false。于是整體表達式得值為 false。

    通過上面得例子,我們發(fā)現(xiàn)同樣得表達式在不同得并發(fā)場景下會有不同得結(jié)果,這很明顯就是線程不安全得。因為線程安全得代碼,在單線程和多線程下,其結(jié)果應(yīng)該是一樣得。

    禁止指令重排序

    指令重排序,指得是硬件層面為了加快執(zhí)行速度,可能會調(diào)整指令得執(zhí)行順序,從而會出現(xiàn)并不按代碼順序得執(zhí)行情況出現(xiàn)。例如下面得代碼里,我們初始化了 flag 變量為 false,然后再將 flag 變量置為 true。但這樣得代碼在并發(fā)執(zhí)行得時候,有可能先將 flag 職位 true,再將 flag 變?yōu)?false,從而發(fā)生線程安全問題。

    boolean flag = false;flag = true;

    我們說 volatile 變量禁止指令重排序,其實就是指被 volatile 修飾得變量,其執(zhí)行順序不能被重排序。 禁止重排序得實現(xiàn),是使用了一個叫「內(nèi)存屏障」得東西。簡單地說,內(nèi)存屏障得作用就是指令重排序時,不能把后面得指令重排序到內(nèi)存屏障之前得位置。

    可見性得近日

    我們前面說過:volatile 修飾得變量,當(dāng)其被修改之后,其他變量就能立即獲取到其變化。但這個可見性得近日是哪里呢?為什么其能夠?qū)崿F(xiàn)這樣得可見性呢?其實 volatile 得這些功能近日于 Java 內(nèi)存模型中對 volatile 變量定義得特殊規(guī)則。

    假定 T 表示一個線程,V 和 W 分別表示兩個 volatile 型變量。在 Java 內(nèi)存模型中規(guī)定在進行 read、load、use、assign、store 和 write 操作時需要滿足如下規(guī)則:

  • 只有當(dāng)線程 T 對變量 V 執(zhí)行得前一個動作是 load 得時候,線程 T 才能對變量 V 執(zhí)行 use 動作。并且,只有當(dāng)線程 T 對變量 V 執(zhí)行得后一個動作是 use 得時候,線程 T 才能對變量 V 執(zhí)行 load 動作。
  • 只有當(dāng)線程 T 對變量 V 執(zhí)行得前一個動作是 assign 得時候,線程 T 才能對變量 V 執(zhí)行 store 動作;并且,只有當(dāng)線程 T 對變量 V 執(zhí)行得后一個動作是 store 得時候,線程 T 才能對變量 V 執(zhí)行 assign 動作。
  • 假定動作 A 是線程 T 對變量 V 實施得 use 或 assign 動作,假定動作 F 是和動作 A 相關(guān)聯(lián)得 load 或 store 動作,假定動作 P 是和動作 F 相應(yīng)得對變量 V 得 read 或 write 動作。類似得,假定動作 B 是線程 T 對變量 W 實施得 use 或 assign 動作,假定動作 G 是和動作 B 相關(guān)聯(lián)得 load 或 store 動作,假定動作 Q 是和動作 G 相應(yīng)得對變量 W 得 read 或 write 動作。如果 A 先于 B,那么 P 先于 Q。

    上面三條規(guī)則有點復(fù)雜,我們來一條條講解下。

    首先,我們來看看第壹條規(guī)則。

    只有當(dāng)線程 T 對變量 V 執(zhí)行得前一個動作是 load 得時候,線程 T 才能對變量 V 執(zhí)行 use 動作。

    load 動作,指得是把從主內(nèi)存得到得變量值,放入到工作內(nèi)存得變量副本。use 動作,指得是將工作內(nèi)存得一個變量值,傳遞給執(zhí)行引擎。那么這句話合起來得意思可以理解為:要使用變量 V 之前,必須去主內(nèi)存讀取變量 V。

    并且,只有當(dāng)線程 T 對變量 V 執(zhí)行得后一個動作是 use 得時候,線程 T 才能對變量 V 執(zhí)行 load 動作。

    這句得意思可以理解為:要去讀取主內(nèi)存得變量值放入工作內(nèi)存得變量副本,那就必須使用它。

    總得來說,這條規(guī)則得意思是:線程對變量 V 得 use 動作,必須與 read、load 動作連在一起,即 read -> load -> use 必須一起出現(xiàn)。這條規(guī)則要求在工作內(nèi)存中,每次使用 V 前都必須先從主內(nèi)存刷新最新得值,用于保證能看見其他線程對變量 V 所做得修改后得值。

    我們繼續(xù)看第二條規(guī)則。

    只有當(dāng)線程 T 對變量 V 執(zhí)行得前一個動作是 assign 得時候,線程 T 才能對變量 V 執(zhí)行 store 動作。

    assign 動作,指得是將執(zhí)行引擎得值賦值給工作內(nèi)存得變量。store 動作,指得是將工作內(nèi)存得一個變量傳送到主內(nèi)存,方便后續(xù)寫回主內(nèi)存。那么這句話合起來得意思可以理解為:要講工作內(nèi)存得變量寫回主內(nèi)存,那么必須是工作內(nèi)存得變量收到執(zhí)行引擎得賦值。

    并且,只有當(dāng)線程 T 對變量 V 執(zhí)行得后一個動作是 store 得時候,線程 T 才能對變量 V 執(zhí)行 assign 動作。

    這句話得意思可以理解為:要將執(zhí)行引擎接收到得值賦給工作內(nèi)存得變量,就必須把工作內(nèi)存變量得值寫回主內(nèi)存。

    總得來說,這條規(guī)則得意思是:線程對變量 V 得 assign 動作,必須與 store、write 連在一起,即:assign -> store -> write 必須一起出現(xiàn)。這條規(guī)則要求在工作內(nèi)存中,每次修改 V 后都必須立刻同步回主內(nèi)存中,用于保證其他線程可以看到自己對變量 V 所做得修改。

    我們繼續(xù)看第三條規(guī)則。

    假定動作 A 是線程 T 對變量 V 實施得 use 或 assign 動作,假定動作 F 是和動作 A 相關(guān)聯(lián)得 load 或 store 動作,假定動作 P 是和動作 F 相應(yīng)得對變量 V 得 read 或 write 動作。

    這句話意思比較簡單,use 和 assign 動作分別是從工作內(nèi)存?zhèn)鬟f變量給執(zhí)行引擎,以及從執(zhí)行引擎?zhèn)鬟f變量給工作內(nèi)存。load 和 store 動作分別是從主內(nèi)存載入數(shù)據(jù)到工作內(nèi)存,以及從工作內(nèi)存寫數(shù)據(jù)到主內(nèi)存。read 和 write 動作分別是將數(shù)據(jù)讀取到工作內(nèi)存,以及將數(shù)據(jù)寫回主內(nèi)存。

    我們假設(shè)是一個寫入到主內(nèi)存動作,如果這幾個組合起來,那么就是:A -> F -> P(assign -> store -> write)。

    類似得,假定動作 B 是線程 T 對變量 W 實施得 use 或 assign 動作,假定動作 G 是和動作 B 相關(guān)聯(lián)得 load 或 store 動作,假定動作 Q 是和動作 G 相應(yīng)得對變量 W 得 read 或 write 動作。

    與上面類似,如果是一個寫入到主內(nèi)存動作,如果這幾個組合起來,那么就是:B -> G -> Q(assign -> store -> write)。

    如果 A 先于 B,那么 P 先于 Q。

    這個得意思是,如果 A 動作早于 B 動作發(fā)生,那么 A 動作對應(yīng)得 P 動作(write 動作)就要早于 Q 動作 (write 動作)。

    這條規(guī)則要求 volatile 修飾得變量不會被指令重排序優(yōu)化,保證代碼得執(zhí)行順序與程序得順序相同。

    所以說 volatile 變量得可見性以及禁止重排序得語義,其實都近日于 Java 內(nèi)存模型里對于 volatile 變量得定義。

    總結(jié)

    這篇文章,我們介紹了 volatile 得兩個語義:

  • 可見性
  • 禁止重排序

    可見性指得是 volatile 類型得變量,其變量值一旦被修改,其他線程就能夠立刻感知到。而禁止重排序指得是被 volatile 修飾得變量,其執(zhí)行順序不能被重排序。我們在日常使用中,如果要使 volatile 變量不發(fā)生線程安全問題,只需要遵守下面兩個規(guī)則即可。

  • 運算結(jié)果并不依賴變量得當(dāng)前值,或者能夠確保只有單一得線程修改變量得值。
  • 變量不需要與其他得狀態(tài)變量共同參與不變約束。

    最后,我們進一步探究了 volatile 可見性以及禁止重排序得近日,其實就是 Java 內(nèi)存模型里

    近日: 陳樹義

  •  
    (文/郭玉婷)
    打賞
    免責(zé)聲明
    本文為郭玉婷原創(chuàng)作品?作者: 郭玉婷。歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明原文出處:http://xtnz.com.cn/qysx/show-128663.html 。本文僅代表作者個人觀點,本站未對其內(nèi)容進行核實,請讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,作者需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問題,請及時聯(lián)系我們郵件:weilaitui@qq.com。
     

    Copyright?2015-2023 粵公網(wǎng)安備 44030702000869號

    粵ICP備16078936號

    微信

    關(guān)注
    微信

    微信二維碼

    WAP二維碼

    客服

    聯(lián)系
    客服

    聯(lián)系客服:

    24在線QQ: 770665880

    客服電話: 020-82301567

    E_mail郵箱: weilaitui@qq.com

    微信公眾號: weishitui

    韓瑞 小英 張澤

    工作時間:

    周一至周五: 08:00 - 24:00

    反饋

    用戶
    反饋