第三章:共享對象
可見性
除了synchronize
,同步還具有另一個重要、微妙的方面:內存可見性。我們不僅希望能夠避免一個線程修改其他線程正在使用的對象的狀態,而且希望確保當一個線程修改了對象的狀態後,其他的線程能夠真正看到改變。
在一個單線程化的環境裏,如果向一個變量先寫入值,然後在沒有寫干涉的情況下讀取這個變量,是可以得到相同的返回值。但是當讀和寫發生在不同的線程中時,情況卻根本不是這樣。
/*
讀線程啓動後,主線程雖然把ready賦值爲true,
但是由於線程之間存在緩存問題,讀線程的reday仍然是false,未能及時獲取最新的reday值。
導致程序的運行結果與預期不符。
*/
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
@Override
public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
鎖和可見性
當線程A執行一個同步塊時,線程B也隨後進入了被同一個鎖監視的同步塊中,這時可以保證,在鎖釋放之前對A可見的變量的值,B獲得鎖之後同樣是可見的。
鎖不僅僅是關於同步與互斥的,也是關於內存可見的。爲了保證所有線程都能夠看到共享的、可變變量的最新值,涉及同一共享變量的讀取和寫入線程必須使用公共的鎖進行同步。
volatile變量
Java語言也提供了其他的選擇,即一種同步的弱形式: volatile
變量。
它確保對一個變量的更新以可預見的方式告知其他的線程。
當一個域聲明爲volatile
類型後,編譯器與運行時會監視這個變量:它是共享的,而且對它的操作不會與其他的內存操作起被重排序。
所以,讀一個volatile
類型的變量時,總會返回由某一線程所寫入的最新值。
訪問volatile
變量的操作不會加鎖,也就不會引起執行線程的阻塞,這使得volatile
變量相對於sychronized
而言,只是輕量級的同步機制。
加鎖可以保證可見性與原子性;
volatile
變量只能保證可見性。
只有滿足了下面所有的標準後,你才能使用volatile
變量:
- 寫入變量時並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值;
- 變量不需要 與其他的狀態變量共同參與不變約束;
- 而且訪問變量時,沒有其他的原因需要加鎖。
備註:原子變量(
java.util.concurrent.atomic
)