Java併發編程-volatile可見性的介紹

前言

要學習好Java的多線程,就必定得對volatile關鍵字的做用機制了熟於胸。最近博主看了大量關於volatile的相關博客,對其有了一點初步的理解和認識,下面經過本身的話敘述整理一遍。bash

有什麼用?

volatile主要對所修飾的變量提供兩個功能多線程

  • 可見性
  • 防止指令重排序


本篇博客主要對volatile可見性進行探討,之後發表關於指令重排序的博文。post

什麼是可見性?

一圖勝千言 學習

JAVA內存模型
上圖已經把JAVA內存模型(JMM)展現得很詳細了,簡單歸納一下

  1. 每一個Thread有一個屬於本身的工做內存(能夠理解爲每一個廚師有一個屬於本身的鐵鍋)
  2. 全部Thread共用一個主內存(餐廳全部的廚師共用同一個冰箱)
  3. 每一個Thread操做數據以前都會去主內存中獲取數據(廚師炒菜以前都要去冰箱裏拿食材)
  • Thread:廚師
  • 工做內存:鐵鍋
  • store&load:放熟食,取食材
  • 主內存:冰箱

讀者可思考如下情景:
餐廳來了一位顧客點了一份紅燒肉,此時有兩位大廚(假設大廚之間互不通訊),因爲互不通訊,因此兩位大廚都打開冰箱取出食材開始炒菜。 最後炒出了兩份紅燒肉,顧客只要一份。爲何會形成這種結果?spa

因爲大廚之間沒有可見性。

將此情景放在JAVA中便是:
線程A從主內存中取了一個變量到工做內存中,操做完畢後沒有及時放回主內存中,因而線程B去取這個變量已經過時了,取的是線程A操做以前的變量。線程

如何擁有可見性?

先介紹一下Java內存模型中定義的8種工做內存與主內存之間的原子操做3d

  • lock( 鎖定 ):做用於主內存的變量,把一個變量標識爲一條線程獨佔的狀態。
  • unlock(解鎖):做用於主內存的變量,把一個處於鎖定的變量釋放出來,釋放變量才能夠被其餘線程鎖定。
  • read(讀取):做用於主內存的變量,把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用。
  • load(載入):做用於***工做內存***的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。
  • use(使用):做用於***工做內***存種的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值的字節碼指令時將會執行這個操做。
  • assign(賦值):做用於***工做內存***中的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
  • store(存儲):做用於***工做內存***的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨後的write操做使用
  • write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的值放入主內存的變量中。
讀取賦值一個普通變量的狀況

普通變量
當線程1對主內存對象發起read操做到write操做套流程的時間裏,線程2隨時都有可能對這個主內存對象發起第二套操做

  • 有什麼危害呢?

假設主內存中有一個code

int a=0;
複製代碼

線程1和線程2分別執行一次,理想狀態下最終a的值爲2.cdn

a++;
複製代碼

線程1在執行了assign操做以後變量a的真實值已經從0變成了1,可是這個過程發生在工做內存中對其餘線程不可見,若線程2此時對變量a的操做,讀取到的值仍然爲0,由於沒有可見性,線程2的操做也僅僅是重複了線程1的操做,再次讓a從0變成了1。並無達到指望的a=2。對象

讀取賦值一個volatile變量的狀況

volatile變量
volatile變量對對象的操做更嚴格:

  • use以前不能被read&load
  • assign以後必須緊跟store&write

也就是說 read-load-useassign-store-write成爲了兩個不可分割的原子操做

儘管這時候在use和assign之間依然有一段真空期,有可能變量會被其餘線程讀取,可是不管在哪個時間點主內存的變量和任一工做內存的變量的值都是相等的。這個特性就致使了volatile變量不適合參與到依賴當前值的運算,如自增。 那麼依靠可見性的特色volatile能夠用在哪些地方呢? 《Java虛擬機》提到:

運算結果並不依賴變量的當前值(即結果對產生中間結果不依賴),或者可以確保只有單一的線程修改變量的值

一般volatile用作保存某個狀態的boolean值。


部分參考自

相關文章
相關標籤/搜索