天天看點

Java Volatile學習

[size=medium]Volatile

Volatile的英文解釋:adj.易變的,不穩定的.

工作記憶體Working Memory: 線程私有空間,在虛拟機棧記憶體裡面,由每個線程獨享。

主記憶體Main Memory: 多個線程共享,在堆記憶體裡面。

對于Volatile類型的變量來說,上一次寫入操作的結果對下一次讀取操作是肯定可見的。在寫入volatile變量值之後,CPU緩存中的内容會被寫回主存,在讀取volatile變量時,CPU緩存中的對應内容被設定為失效狀态,重新從主存中進行讀取。将變量聲明為volatile相當于為單個變量的讀取和寫入添加了同步操作。但是volatile在使用時不需要利用鎖機制,是以性能要由于synchronized關鍵詞。[/size]

public class Worker {
	private volatile boolean done;
	public void setDone(boolean done) {
		this.done = done;
	}
	public void work() {
		while (!done) {
			//do something
		}
	}
}
           

[size=medium]對于上面的例子,當線程A調用lWorker類的對象的work方法,開始執行具體的任務。在适當的時候線程B會調用同一Work類的對象的setDone方法來聲明終止任務的執行。把done變量聲明為volatile是很重要的。隻有這樣才能保證線程B對done變量所做的修改對線程A的後續操作是可見的。否則,線程A可能由于無法看到done變量值的變化而一直運作下去。

但是雖然volatile關鍵詞使用簡單,但是由于在實作時沒有鎖機制的存在,volatile關鍵詞的适用場景是受限的。比如對于下面的例子:[/size]

public class IdGenerator {
	private int value = 0;
	public int getNext() {
		return value++;
	}
}
           

[size=medium]注釋: 雖然getNext方法隻有一行代碼,但是這一行代碼對應的位元組碼指令卻是7條。

如果隻是把value聲明為volatile是不夠的,仍然會出現問題。這是因為寫入的value的正确值依賴于value的目前值,而目前值有可能是不正确的。假設線程A擷取了value的目前值1卻發生了線程切換,如果線程B把value改成2後,線程A才獲得了執行,這個時候A所持有的目前值1就已經不是正确的了。[color=darkred][b]當要寫入的新值與目前值沒有關系時,使用volatile就足夠了。[/b][/color]

原子操作:

在Java中,對于非long型和double型的域的讀取和寫入操作是原子操作。對象引用的讀取和寫入操作也是原子操作。比如讀取一個int型的域時,該域對應的記憶體位址中的32位的内容會被完整讀取,在讀取過程中不會被其他線程所打斷。在寫入操作時也不會被打斷。在寫入非volatile的long型和double型的域的值時,分成兩次操作來完成。一個long型或double型的域的長度是64位,每次寫入32位。在一個線程寫入了long型或double型的域的前32位之後,在寫入後32位之前,另外一個線程有可能通路這個域的值,進而讀取隻完成部分寫入操作的錯誤值。是以在多線程程式中使用long型和double型的共享變量時,需要把變量聲明為volatile,以保證讀取和寫入的完整性。

另外注意這裡說的類型時基本類型,并不是封裝類型,封裝類型事實上是object.

可見性:[/size]

繼續閱讀