volatile 也是 多線程的解決方案之一。volatile 能夠保證 可見性,但是不能保證原子性。它只能作用於變量,不能作用於方法。當一個變量被聲明爲 volatile 的時候,任何 對該變量的讀寫都會繞過 高速緩存,直接讀取主內存的變量的值。
如何理解直接讀寫主內存的值:回到 多線程產生的原因 ,在 i++ 操作的時候,當 進行 執行引擎 對 變量 進行 + 1 之後,原來 是應該寫入到 本地內存中,再由本地內存寫入到主內存中,但是 由於 變量使用了 volatile 的修飾,所以 該值不會經過本地內存,而是直接寫入到 主內存中去。 讀取也是同樣的道理。
使用volatile 有兩點需要注意的地方:
- 運算結果並不依賴於當前值,或者能確保只有單一的線程能夠修改變量的值。
- 變量不需要和其他的狀態變量共同參與不變約束
public class main {
public static int i = 0;
public static void main(String args[]){
new Thread(new Runnable(){
public void run(){
for(int j = 0; j < 10000; j++)
i++;
System.out.println("Thread1 end...");
}
}).start();;
new Thread(new Runnable(){
public void run(){
for(int j = 0; j < 10000; j++)
i++;
System.out.println("Thread2 end...");
}
}).start();
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
,
System.out.println("i = " + i);
}
}
以上程序不一定全會打印出 20000.分析如下:- 正常情況下:A 線程取得 主內存的值 i,進行修改,再寫回到 主內存,B線程 取得主內存的值 i,進行修改,再寫回主內存。這種情況下是正常打印出 20000。
- 非正常情況下:A線程取得 主內存的值 i, B 內存取得主內存的值 i,A線程修改後寫回主內存,B線程修改後寫回主內存。這種情況下,結果就少了 1.
對於第二點的理解:
private Date start;
private Date end;
public void setInterval(Date newStart, Date newEnd) {
// 檢查start<end是否成立, 在給start賦值之前不變式是有效的
start = newStart;
// 但是如果另外的線程在給start賦值之後給end賦值之前時檢查start<end, 該不變式是無效的
end = newEnd;
// 給end賦值之後start<end不變式重新變爲有效
}
最後,關於什麼時候使用 volatile,一般是用來當做標記來使用。比如說,當shutdown() 方法被調用的時候,所有的 doWork() 方法都會停下來。