1. 程式人生 > 其它 >理解 volatile 關鍵字

理解 volatile 關鍵字

官方定義

This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

官方的描述只看懂了,前半部分,意思是,volatile 修飾的變數總是對其他執行緒是可見的,後半部分理解不了,不過沒關係我們繼續探索。

什麼是可見性?

說到可見性需要對 JMM 模型 和 硬體記憶體架構 有所瞭解

可以看一下 JMM 和 硬體記憶體架構 的內容

上圖就是 現代硬體記憶體架構,我們圍繞這個來討論可見性

  • JVM 執行緒是實實在在的綁定了 OS 執行緒的,即我們通過程式碼 Thread.start() 就會向 OS 申請並繫結一個OS執行緒
  • 由於CPU 暫存器上的執行速度遠大於主存中的讀寫速度,所以需要在兩者之間新增快取記憶體,提高 CPU 利用率
  • CPU 計算的流程如下
    • 當 CPU 切換到某個執行緒,而且改執行緒需要進行運算 i = i+1
    • 先將需要的 i 從主存載入到快取記憶體
    • CPU 暫存器會 複製
      i 的值 到 CPU 暫存器中
    • CPU 暫存器自增加 1
    • 將結果賦值給快取記憶體層 中的 i
    • 快取記憶體層在刷回到主存中

如果在單執行緒環境下沒有問題,但是在多執行緒環境下,就會導致一個問題,在不同的執行緒中快取記憶體中 i 的值會出現不一致,

即 線上程 t1 計算完 +1 操作但是還沒刷回主存這段時間之前 t2 獲取到值還是原來的 i

所以就有了 volatile ,它的作用是 如果執行緒 t1修改了 i,會馬上刷回主存,並且其他執行緒中快取記憶體中的 i 值失效,需要重新從主存中載入,這樣就所有執行緒中的快取記憶體 i 值都是一樣的

一句話概括

volatile 修飾的變數就是讓所有執行緒中的快取記憶體中該變數都是相同值

volatile 等於 原子性嗎?

我們換個說法,就是volatile 修飾的變數執行緒安全嗎? 答案是否定的!

我們注意思考上的概括,說的是快取記憶體中的變數是相同的,沒有說已經被暫存器複製的變數副本!!

假設 i == 1 在主存中,現在需要對 i 自增 1 兩次,我們採用了兩個執行緒來執行,時間順序如下

  • t1 和 t2 執行緒啟動
  • t1,執行緒開始執行,從主存載入資料到快取記憶體,暫存器從快取記憶體複製 i的值 1 到暫存器自加1,這時 i == 2
  • 暫存器將值賦給t1 執行緒中的快取記憶體i,這時快取記憶體的 i值為 2
  • 這時 t1 進行了上下文切換,切換到了其他執行緒
  • t2 執行緒開始被另一個CPU 核 載入
  • t2 執行緒開始執行,從主存載入資料到快取記憶體,暫存器從快取記憶體複製 i的值 1 到暫存器,這時暫存器的值是 1
  • t2 被其他執行緒切換
  • t1 被排程,作業系統恢復了剛剛 t1 的狀態和資料,由於 i是被volatile 修飾,需要刷回主存
  • 這時 主存中 的 i == 2
  • t2 被排程作業系統恢復了剛剛 t2 的狀態和資料(快取記憶體 i == 1,暫存器中的值==1)
  • 由於 i是被volatile 修飾,所以快取記憶體中的失效了,需要從主存載入,載入後的 t2 資料(快取記憶體 i == 2,暫存器中的值==1)
  • t2 繼續執行,自加 1 ,賦值給 t2 的快取記憶體變數 i == 2
  • t2 將變數刷回主存,i == 2

好的,看到問題了,自加了兩次的結果等於2 執行緒不安全!!

那 volatile 有什麼用?

可見性並不能解決執行緒安全問題啊??,加了還不如不加?

其實不然

  • volatile & cas 就能解決執行緒安全問題(AtomicInteger 類就是例子)
  • volatile 的可見性,如有改動需要馬上刷回主存,這個可以用來做執行緒間的通訊
  • volatile對指令重排的影響

問題

Java 多執行緒在單核CPU,volatile 是否還有必要呢?

如果快取記憶體區多個執行緒共享

  • 那麼volatile的記憶體可見特性就顯得無關緊要——因為不同執行緒無需通過主記憶體進行通訊
  • volatile對指令重排的影響,確保volatile的寫-讀和監視器的釋放-獲取一樣,具有相同的記憶體語義

參考

java多執行緒在單核CPU上,還是需要volatile synchronized嗎?

Java記憶體模型(JMM)總結