volatile關鍵字的作用、原理
在只有雙重檢查鎖,沒有volatile的懶加載單例模式中,由於指令重排序
的問題,我確實不會拿到兩個不同的單例
了,但我會拿到“半個”單例
。
而發揮神奇作用的volatile,可以當之無愧的被稱爲Java併發編程中“出現頻率最高的關鍵字”,常用於保持內存可見性和防止指令重排序。
保持內存可見性
內存可見性(Memory Visibility):所有線程都能看到共享內存的最新狀態。
失效數據
以下是一個簡單的可變整數類:
public class MutableInteger {
private int value;
public int get(){
return value;
}
public void set(int value){
this.value = value;
}
}
MutableInteger
不是線程安全的,因爲get
和set
方法都是在沒有同步的情況下進行的。如果線程1調用了set方法,那麼正在調用的get的線程2可能會看到更新後的value值,也可能看不到。
解決方法很簡單,將value
聲明爲volatile
變量:
private volatile int value;
神奇的volatile關鍵字
神奇的volatile關鍵字解決了神奇的失效數據問題。
Java變量的讀寫
Java通過幾種原子操作完成工作內存
和主內存
的交互:
- lock:作用於主內存,把變量標識爲線程獨佔狀態。
- unlock:作用於主內存,解除獨佔狀態。
- read:作用主內存,把一個變量的值從主內存傳輸到線程的工作內存。
- load:作用於工作內存,把read操作傳過來的變量值放入工作內存的變量副本中。
- use:作用工作內存,把工作內存當中的一個變量值傳給執行引擎。
- assign:作用工作內存,把一個從執行引擎接收到的值賦值給工作內存的變量。
- store:作用於工作內存的變量,把工作內存的一個變量的值傳送到主內存中。
- write:作用於主內存的變量,把store操作傳來的變量的值放入主內存的變量中。
volatile如何保持內存可見性
volatile的特殊規則就是:
- read、load、use動作必須連續出現。
- assign、store、write動作必須連續出現。
所以,使用volatile變量能夠保證:
- 每次
讀取前
必須先從主內存刷新最新的值。 - 每次
寫入後
必須立即同步回主內存當中。
也就是說,volatile關鍵字修飾的變量看到的隨時是自己的最新值。線程1中對變量v的最新修改,對線程2是可見的。
volatile爲什麼沒有原子性
例如你讓一個volatile的integer自增(i++),其實要分成3步:1)讀取volatile變量值到local; 2)增加變量的值;3)把local的值寫回,讓其它的線程可見。這期間主內存中的值可能被其他修改,再將值覆蓋會導致其失效。AtomicXXX具原子性是使用了CAS(比較並交換)。