在多線程併發編程中 synchronized
和 volatile
都扮演着重要的角色,volatile
是輕量級的 synchronized
,它在多處理器開發中保證了共享變量的“可見性”。
內存可見性
- 內存可見性,指的是線程之間的可見性,當一個線程修改了共享變量時,另一個線程可以讀取到這個修改後的值。
- 所謂內存可見性,指的是當一個線程對
volatile
修飾的變量進行寫操作(比如step 2)時,會立即把該線程對應的本地內存中的共享變量的值刷新到主內存;當一個線程對volatile
修飾的變量進行讀操作(比如step 3)時,會把立即該線程對應的本地內存置爲無效,從主內存中讀取共享變量的值。
public class VolatileDemo {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // step 1
flag = true; // step 2
}
public void reader() {
if (flag) { // step 3
System.out.println(a); // step 4
}
}
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo volatileDemo = new VolatileDemo();
Thread a = new Thread(new Runnable() {
@Override
public void run() {
volatileDemo.writer();
}
});
Thread b = new Thread(new Runnable() {
@Override
public void run() {
volatileDemo.reader();
}
});
a.start();
b.start();
}
這個時候的輸出結果爲:1
而如果flag變量沒有用volatile修飾,在step 2,線程A的本地內存裏面的變量就不會立即更新到主內存,那隨後線程B也同樣不會去主內存拿最新的值,仍然使用線程B本地內存緩存的變量的值a = 0,flag = false。
禁止重排序
爲優化程序性能,對原有的指令執行順序進行優化重新排序。重排序可能發生在多個階段,比如編譯重排序、CPU重排序等。
在JSR-133之前的舊的Java內存模型中,是允許volatile變量與普通變量重排序的。那上面的案例中,可能就會被重排序成下列時序來執行:
- 線程A寫volatile變量,step 2,設置flag爲true;
- 線程B讀同一個volatile,step 3,讀取到flag爲true;
- 線程B讀普通變量,step 4,讀取到 a = 0;
- 線程A修改普通變量,step 1,設置 a = 1;
可見,如果volatile變量與普通變量發生了重排序,雖然volatile變量能保證內存可見性,也可能導致普通變量讀取錯誤。
所以在舊的內存模型中,volatile的寫-讀就不能與鎖的釋放-獲取具有相同的內存語義了。爲了提供一種比鎖更輕量級的線程間的通信機制,JSR-133專家組決定增強volatile的內存語義:嚴格限制編譯器和處理器對volatile變量與普通變量的重排序。
volatile的非原子性
volatile
雖然增加了實例變量在多個線程之間的可見性,但是卻不具備通同步性,也就是不具備原子性。- 使用
volatile
關鍵字之後,可以強制從公共內存中讀取變量的值。 Volatile
只能保證對單個volatile
變量的讀/寫具有原子性。
volatile的用途
volatile的主要使用場合是在多個線程中可以感知到實例變量被更改了,也就是多線程讀取共享變量的時可以獲得最新的值來使用。
在保證內存可見性這一點上,volatile
有着與鎖相同的內存語義,所以可以作爲一個“輕量級”的鎖來使用。但由於volatile
僅僅保證對單個volatile
變量的讀/寫具有原子性,而鎖可以保證整個臨界區代碼的執行具有原子性。所以在功能上,鎖比volatile
更強大;在性能上,volatile更有優勢。
比如我們熟悉的單例模式,其中有一種實現方式是“雙重鎖檢查”,比如這樣的代碼:
public class Singleton {
private static volatile Singleton instance; // 不使用volatile關鍵字
// 雙重鎖檢驗
public static Singleton getInstance() {
if (instance == null) { // 第7行
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 第10行
}
}
}
return instance;
}
}
synchronized和volatile對比
- volatile是線程同步的輕量級的實現,所以volatile性能比synchronized要好,並且volatile只能修飾變量,而synchronized可以修飾方法以及代碼塊。
- 多線程訪問volatile不會發生阻塞,而synchronized會發生阻塞。
- volatile能保證數據的可見性,但不能保證原子性。而synchronized可以保證原子性,也可以間接保證可見性,因爲synchronized會將私有內存和公共內存中的數據做同步,所以更加推薦使用synchronized。
- volatile的關注點在於解決變量在多個線程之間的可見性,而synchronized的關注點在於解決多個線程之間訪問資源的同步性。