高併發程式設計:volatile 詳解
主記憶體與工作記憶體
在介紹volatile之前,先簡單瞭解一下Java記憶體模型。在Java虛擬機器規範中試圖定義一種Java記憶體模型(Java Memory Model,JMM)來遮蔽各個硬體平臺和作業系統的記憶體訪問差異,以實現讓Java程式在各種平臺下都能達到一致的記憶體訪問效果,筆者認為是定義了程式中變數的訪問規則。
主記憶體
所有執行緒共享的區域,儲存執行緒共享的資料,包括例項變數、靜態變數和構成陣列的物件的元素,不包括區域性變數和方法引數。
工作記憶體
每個執行緒獨享的區域,儲存主記憶體中的資料拷貝。
Java記憶體模型規定所有的變數都是存在主記憶體中,每個執行緒都有自己的工作記憶體。每個執行緒對共享資料的讀、寫操作都只在各自的工作記憶體中,不能直接在主記憶體中進行;在各自工作記憶體中對資料操作完成後,同步到主記憶體中;執行緒間不能訪問各自工作記憶體中的資料,只能通過主記憶體來完成。下面用一張圖來展示執行緒、主記憶體和工作記憶體三者之間的互動關係。
volatile
當使用volatile關鍵字修飾共享變數(例項變數、靜態變數)時,它將具備兩個特性:可見性和禁止指令重排序優化。
可見性
變數被修改後的新值會立即寫回主記憶體中,同時會使其它執行緒工作記憶體中的舊值失效,新值對其它執行緒來說是可見的,其它執行緒可以立即得到修改後的新值,因為volatile變數每次使用之前都需要從主記憶體重新整理獲取最新值。
關於volatile實現的可見性可能會誤解,認為既然volatile變數所有的寫操作都會立刻反應到其它執行緒中,那麼對volatile變數進行併發操作就是安全的。有這個誤解是因為忽略了原子性,volatile是不保證原子性的。對一個變數進行修改賦值操作,可能寫的就是一條簡單的i=i+1,但是底層實現上會需要多條位元組碼指令來完成,同時一條位元組碼指令也可能轉化成多條機器碼指令,在併發情況下,這些指令的執行不能保證原子性。
禁止指令重排序優化
指令重排序是指CPU在正確處理指令依賴(資料依賴)並且保障程式執行得到正確結果的情況下,調整程式碼的執行順序,允許將多條指令不按照程式規定順序分開發送給各相應電路單元處理。需要注意的是指令重排序不會影響到程式碼在單執行緒環境下的執行,會影響到多執行緒併發情況下執行的正確性。
volatile禁止指令重排序是通過lock字首指令實現的,lock字首的指令相當於一個記憶體屏障,指令重排序時不能把後面的指令重排序到lock字首指令之前,同時它會強制將對工作記憶體的修改操作立即寫入主記憶體中。
使用條件
雖然volatile可以實現最輕量級的同步機制,但是使用volatile修飾的變數必須滿足以下兩個條件:
-
對變數的寫操作不依賴於當前值,或者確保只有一個執行緒修改變數的值;
-
該變數沒有包含在具有其他變數的不變式中。
往期精彩內容
-
360c9c4cf14bec405e6133f4d14ab700a74eda6322629ee8ce8806ba&scene=21#wechat_redirect" target="_blank" rel="nofollow,noindex">高併發程式設計-AQS深入解析
覺得有收穫,誠邀 關注、點贊、轉發 。