天天看點

轉Java中關鍵字volatile

在Java中設定變量值的操作,除了long和double類型的變量外都是原子操作,也就是說,對于變量值的簡單讀寫操作沒有必要進行同步。

這在JVM 1.2之前,Java的記憶體模型實作總是從主存讀取變量,是不需要進行特别的注意的。而随着JVM的成熟和優化,現在在多線程環境下volatile關鍵字的使用變得非常重要。

在目前的Java記憶體模型下,線程可以把變量儲存在本地記憶體(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成資料的不一緻。

要解決這個問題,隻需要像在本程式中的這樣,把該變量聲明為volatile(不穩定的)即可,這就訓示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的标志都應該加volatile修飾。

Volatile修飾的成員變量在每次被線程通路時,都強迫從共享記憶體中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程将變化值回寫到共享記憶體。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

Java語言規範中指出:為了獲得最佳速度,允許線程儲存共享成員變量的私有拷貝,而且隻當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。

這樣當多個線程同時與某個對象互動時,就必須要注意到要讓線程及時的得到共享成員變量的變化。

而volatile關鍵字就是提示VM:對于這個成員變量不能儲存它的私有拷貝,而應直接與共享成員變量互動。

使用建議:在兩個或者更多的線程通路的成員變量上使用volatile。當要通路的變量已在synchronized代碼塊中,或者為常量時,不必使用。

由于使用volatile屏蔽掉了VM中必要的代碼優化,是以在效率上比較低,是以一定在必要時才使用此關鍵字。

在Java記憶體模型中,有main memory,每個線程也有自己的memory (例如寄存器)。為了性能,一個線程會在自己的memory中保持要通路的變量的副本。這樣就會出現同一個變量在某個瞬間,在一個線程的memory中的值可能與另外一個線程memory中的值,或者main memory中的值不一緻的情況。   一個變量聲明為volatile,就意味着這個變量是随時會被其他線程修改的,是以不能将它cache線上程memory中。以下例子展現了volatile的作用: Java代碼

轉Java中關鍵字volatile
  1. public class StoppableTask extends Thread {    
  2.      private volatile boolean pleaseStop;   
  3.      public void run() {    
  4.              while (!pleaseStop) {    
  5.                     // do some stuff...         
  6.               }   
  7.      }    
  8.      public void tellMeToStop() {      
  9.              pleaseStop = true;    
  10.      }   
  11. }  

    假如pleaseStop沒有被聲明為volatile,線程執行run的時候檢查的是自己的副本,就不能及時得知其他線程已經調用tellMeToStop()修改了pleaseStop的值。   Volatile一般情況下不能代替sychronized,因為volatile不能保證操作的原子性,即使隻是i++,實際上也是由多個原子操作組成:read i; inc; write i,假如多個線程同時執行i++,volatile隻能保證他們操作的i是同一塊記憶體,但依然可能出現寫入髒資料的情況。如果配合Java 5增加的atomic wrapper classes,對它們的increase之類的操作就不需要sychronized。