天天看點

讀深入了解JAVA虛拟機-volatile特殊規則

  1. volatile關鍵字是JVM提供最輕量級的同步機制!!!
  2. volatile關鍵字具備兩種特性:
    • 保證變量對所有線程的可見性
    • 禁止指令重排序優化
    • 千萬注意:因為java中的運算不具備原子性,不能保證線程安全,不能保證線程安全。
  • 可見性
    • 當一個線程修改了變量的值,新值對于其他線程是可以立即得知的。
    • 在各個線程的工作記憶體中,volatile變量也可以存在不一緻的情況,但由于每次使用變量前都要先從主記憶體重新整理,JVM的執行引擎看不到不一緻的情況,是以可以認為不存在一緻性問題。
    • 一般來說,在以下兩條規則下使用volatile才可以保證線程是安全的:
      • 運算結果并不依賴變量的目前值或者能保證隻有單線程修改變量的值
      • 變量不需要與其他的狀态變量共同參與不變的限制(暫時不是很了解???)
  • 禁止指令重排序
    • 正常普通的變量僅僅會保證在該方法的執行過程中所有依賴指派結果的地方能擷取到正常的結果,不能保證變量指派的操作和程式代碼中的執行順序一緻
int a = 1;	//...1
int b = 2;	//...2
int c = 3;	//...3
//以上三行代碼時毫無依賴關系的,是以在JVM執行的順序就有可能是 321 or 231。。。
           

Q:那麼volatile關鍵字是如何保證“可見性”和“指令重排序的”?

  • 被volatile修飾的變量,在被指派後,彙編代碼會多執行一個“lock addl $0x0,(%esp)”操作,該指令相當于一個“記憶體屏障”(它不允許屏障後面的指令被重排序到屏障前)。“lock addl $0x0,(%esp)”的語義是把ESP寄存器的值加“0”,是一個空操作。關鍵在于lock這個字首,IA32手冊(是因特爾32位體系的一個架構,不是很懂)中,它的作用是使得本CPU的Cache寫入了記憶體時,該寫入動作也會引起别的CPU或者别的核心無效化其Cache。這相當于Cache中的變量做了一次“store和write”操作。通過這樣一個空操作,讓volatile變量的修改對其他CPU立即可見。
  • 而“禁止指令重排序”,指令重排序,并不是說指令可以任意重排,CPU需要能夠正确處理 “依賴情況” 以保證程式能夠得到正确的結果。而“lock addl $0x0,(%esp)”會把修改同步到主記憶體中,意味着之前的操作均已完成(相當于前後産生了“依賴”),這樣就築起了“記憶體屏障”。而volatile變量之前的指令個人了解還是可以重排序,但不能排到volatile變量指派動作的後面去,因為volatile變量之前的指令重排序并不會影響volatile變量的結果正确性。

Volatile基本原則及選擇依據

  • volatile變量的讀操作與普通變量基本沒有查詢
  • volatile變量的寫操作與普通變量稍微慢一些(畢竟要多做一些動作保證可見性和禁止指令重排序)
  • 選擇依據:volatile關鍵字的語義是否能夠滿足你的使用場景

Java記憶體模型的基本操作對Volatile變量的規則限制

  • 一個線程對volatile變量進行use操作時,必須先進行read和load操作(保證值是從主記憶體拿的且是最新的)
  • 一個線程對volatile變量進行了assign操作,必須進行store和write操作(保證主記憶體中值是最新的)