漫畫:什么是 volatile 關鍵字?

      網友投稿 627 2025-04-10

      漫畫:什么是 volatile 關鍵字?

      Java內存模型簡稱JMM(Java Memory Model),是Java虛擬機所定義的一種抽象規范,用來屏蔽不同硬件和操作系統的內存訪問差異,讓java程序在各種平臺下都能達到一致的內存訪問效果。

      Java內存模型長成什么樣子呢?就是下圖的樣子:

      這里需要解釋幾個概念:

      1.主內存(Main Memory)

      主內存可以簡單理解為計算機當中的內存,但又不完全等同。主內存被所有的線程所共享,對于一個共享變量(比如靜態變量,或是堆內存中的實例)來說,主內存當中存儲了它的“本尊”。

      2.工作內存(Working Memory)

      工作內存可以簡單理解為計算機當中的CPU高速緩存,但又不完全等同。每一個線程擁有自己的工作內存,對于一個共享變量來說,工作內存當中存儲了它的“副本”。

      線程對共享變量的所有操作都必須在工作內存進行,不能直接讀寫主內存中的變量。不同線程之間也無法訪問彼此的工作內存,變量值的傳遞只能通過主內存來進行。

      以上說的這些可能有點抽象,大家來看看下面這個例子:

      對于一個靜態變量

      static int s = 0;

      線程A執行如下代碼:

      s = 3;

      那么,JMM的工作流程如下圖所示:

      通過一系列內存讀寫的操作指令(JVM內存模型共定義了8種內存操作指令,以后會細講),線程A把靜態變量 s=0 從主內存讀到工作內存,再把 s=3 的更新結果同步到主內存當中。從單線程的角度來看,這個過程沒有任何問題。

      這時候我們引入線程B,執行如下代碼:

      System.out.println("s=" + s);

      引入線程B以后,當線程A首先執行,更大的可能是出現下面情況:

      此時線程B從主內存得到的s值是3,理所當然輸出 s=3,這種情況不難理解。但是,有較小的幾率出現另一種情況:

      因為工作內存所更新的變量并不會立即同步到主內存,所以雖然線程A在工作內存當中已經把變量s的值更新成3,但是線程B從主內存得到的變量s的值仍然是0,從而輸出 s=0。

      volatile關鍵字具有許多特性,其中最重要的特性就是保證了用volatile修飾的變量對所有線程的可見性。

      這里的可見性是什么意思呢?當一個線程修改了變量的值,新的值會立刻同步到主內存當中。而其他線程讀取這個變量的時候,也會從主內存中拉取最新的變量值。

      為什么volatile關鍵字可以有這樣的特性?這得益于java語言的先行發生原則(happens-before)。先行發生原則在維基百科上的定義如下:

      In computer science, the happened-before relation is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow).

      翻譯結果如下:

      在計算機科學中,先行發生原則是兩個事件的結果之間的關系,如果一個事件發生在另一個事件之前,結果必須反映,即使這些事件實際上是亂序執行的(通常是優化程序流程)。

      這里所謂的事件,實際上就是各種指令操作,比如讀操作、寫操作、初始化操作、鎖操作等等。

      先行發生原則作用于很多場景下,包括同步鎖、線程啟動、線程終止、volatile。我們這里只列舉出volatile相關的規則:

      對于一個volatile變量的寫操作先行發生于后面對這個變量的讀操作。

      回到上述的代碼例子,如果在靜態變量s之前加上volatile修飾符:

      volatile static int s = 0;

      線程A執行如下代碼:

      s = 3;

      這時候我們引入線程B,執行如下代碼:

      System.out.println("s=" + s);

      當線程A先執行的時候,把s = 3寫入主內存的事件必定會先于讀取s的事件。所以線程B的輸出一定是s = 0。

      這段代碼是什么意思呢?很簡單,開啟10個線程,每個線程當中讓靜態變量count自增100次。執行之后會發現,最終count的結果值未必是1000,有可能小于1000。

      使用volatile修飾的變量,為什么并發自增的時候會出現這樣的問題呢?這是因為count++這一行代碼本身并不是原子性操作,在字節碼層面可以拆分成如下指令:

      getstatic????????//讀取靜態變量(count)

      iconst_1????????//定義常量1

      iadd????????????? ?//count增加1

      putstatic????????//把count結果同步到主內存

      雖然每一次執行?getstatic 的時候,獲取到的都是主內存的最新變量值,但是進行iadd的時候,由于并不是原子性操作,其他線程在這過程中很可能讓count自增了很多次。這樣一來本線程所計算更新的是一個陳舊的count值,自然無法做到線程安全:

      因此,什么時候適合用volatile呢?

      1.運行結果并不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。

      2.變量不需要與其他的狀態變量共同參與不變約束。

      第一條很好理解,就是上面的代碼例子。第二條是什么意思呢?可以看看下面這個場景:

      volatile static int start = 3;

      volatile?static?int end = 6;

      線程A執行如下代碼:

      while (start < end){

      //do something

      }

      線程B執行如下代碼:

      start+=3;

      end+=3;

      這種情況下,一旦在線程A的循環中執行了線程B,start有可能先更新成6,造成了一瞬間?start == end,從而跳出while循環的可能性。

      幾點補充:

      1. 關于volatile的介紹,本文很多內容來自《深入理解Java虛擬機》這本書。有興趣的同學可以去看看。

      2.本漫畫純屬娛樂,還請大家盡量珍惜當下的工作,切勿模仿小灰的行為哦。

      —————END—————

      喜歡本文的朋友們,歡迎長按下圖關注訂閱號程序員小灰,收看更多精彩內容

      Java 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:怎么弄背景圖片(如何把圖片背景)
      下一篇:單元格內的文字變成兩行,怎么辦(如何把單元格里的字變成兩行)
      相關文章
      亚洲日本成本人观看| 亚洲国产成人精品久久| 亚洲色大成WWW亚洲女子| 亚洲日日做天天做日日谢| 亚洲网址在线观看| 亚洲视频日韩视频| 亚洲黄色免费观看| 91大神亚洲影视在线| 久久亚洲日韩精品一区二区三区| 精品国产综合成人亚洲区| 亚洲色婷婷综合久久| 亚洲精品无码久久久影院相关影片| 国产亚洲精品无码拍拍拍色欲| 亚洲国产精品综合久久网络| 亚洲国产综合精品中文字幕 | 亚洲成A∨人片天堂网无码| 国产亚洲情侣久久精品| 亚洲AV永久无码精品一区二区国产 | 亚洲综合AV在线在线播放| 亚洲小说区图片区另类春色| 亚洲国产精品高清久久久| 亚洲第一极品精品无码久久| 久久精品亚洲视频| 91亚洲导航深夜福利| 亚洲国产成人精品久久| 2020久久精品亚洲热综合一本| 亚洲中文字幕久久久一区| 亚洲AV性色在线观看| 亚洲成a人片在线观看久| 国产亚洲精品精品国产亚洲综合 | 亚洲啪啪免费视频| 亚洲国产精品一区二区三区在线观看| 四虎亚洲精品高清在线观看| 亚洲精品欧美综合四区| 国产亚洲视频在线| 国产亚洲精久久久久久无码77777 国产亚洲精品成人AA片新蒲金 | 亚洲av午夜精品一区二区三区 | 亚洲欧美国产精品专区久久| 精品国产_亚洲人成在线| 亚洲另类激情专区小说图片| 亚洲人成精品久久久久|