volatile用處說明
在JDK1.2之前,Java的內存模型實現總是從主存(即共享內存)讀取變量,是不需要進行特別的注意的。而隨着JVM的成熟和優化,現在在多線程環境下volatile關鍵字的使用變得非常重要。
示例程序
下面給出一段代碼,通過其運行結果來說明使用關鍵字volatile產生的差異,但實際上遇到了意料之外的問題:
- public class Volatile extends Object implements Runnable {
- //value變量沒有被標記爲volatile
- private int value;
- //missedIt變量被標記爲volatile
- private volatile boolean missedIt;
- //creationTime不需要聲明爲volatile,因爲代碼執行中它沒有發生變化
- private long creationTime;
- public Volatile() {
- value = 10;
- missedIt = false;
- //獲取當前時間,亦即調用Volatile構造函數時的時間
- creationTime = System.currentTimeMillis();
- }
- public void run() {
- print("entering run()");
- //循環檢查value的值是否不同
- while ( value < 20 ) {
- //如果missedIt的值被修改爲true,則通過break退出循環
- if ( missedIt ) {
- //進入同步代碼塊前,將value的值賦給currValue
- int currValue = value;
- //在一個任意對象上執行同步語句,目的是爲了讓該線程在進入和離開同步代碼塊時,
- //將該線程中的所有變量的私有拷貝與共享內存中的原始值進行比較,
- //從而發現沒有用volatile標記的變量所發生的變化
- Object lock = new Object();
- synchronized ( lock ) {
- //不做任何事
- }
- //離開同步代碼塊後,將此時value的值賦給valueAfterSync
- int valueAfterSync = value;
- print("in run() - see value=" + currValue +", but rumor has it that it changed!");
- print("in run() - valueAfterSync=" + valueAfterSync);
- break;
- }
- }
- print("leaving run()");
- }
- public void workMethod() throws InterruptedException {
- print("entering workMethod()");
- print("in workMethod() - about to sleep for 2 seconds");
- Thread.sleep(2000);
- //僅在此改變value的值
- value = 50;
- print("in workMethod() - just set value=" + value);
- print("in workMethod() - about to sleep for 5 seconds");
- Thread.sleep(5000);
- //僅在此改變missedIt的值
- missedIt = true;
- print("in workMethod() - just set missedIt=" + missedIt);
- print("in workMethod() - about to sleep for 3 seconds");
- Thread.sleep(3000);
- print("leaving workMethod()");
- }
- /*
- *該方法的功能是在要打印的msg信息前打印出程序執行到此所化去的時間,以及打印msg的代碼所在的線程
- */
- private void print(String msg) {
- //使用java.text包的功能,可以簡化這個方法,但是這裏沒有利用這一點
- long interval = System.currentTimeMillis() - creationTime;
- String tmpStr = " " + ( interval / 1000.0 ) + "000";
- int pos = tmpStr.indexOf(".");
- String secStr = tmpStr.substring(pos - 2, pos + 4);
- String nameStr = " " + Thread.currentThread().getName();
- nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length());
- System.out.println(secStr + " " + nameStr + ": " + msg);
- }
- public static void main(String[] args) {
- try {
- //通過該構造函數可以獲取實時時鐘的當前時間
- Volatile vol = new Volatile();
- //稍停100ms,以讓實時時鐘稍稍超前獲取時間,使print()中創建的消息打印的時間值大於0
- Thread.sleep(100);
- Thread t = new Thread(vol);
- t.start();
- //休眠100ms,讓剛剛啓動的線程有時間運行
- Thread.sleep(100);
- //workMethod方法在main線程中運行
- vol.workMethod();
- } catch ( InterruptedException x ) {
- System.err.println("one of the sleeps was interrupted");
- }
- }
- }
按照以上的理論來分析,由於value變量不是volatile的,因此它在main線程中的改變不會被Thread-0線程(在main線程中新開啓的線程)馬上看到,因此Thread-0線程中的while循環不會直接退出,它會繼續判斷missedIt的值,由於missedIt是volatile的,當main線程中改變了missedIt時,Thread-0線程會立即看到該變化,那麼if語句中的代碼便得到了執行的機會,由於此時Thread-0依然沒有看到value值的變化,因此,currValue的值爲10,繼續向下執行,進入同步代碼塊,因爲進入前後要將該線程內的變量值與共享內存中的原始值對比,進行校準,因此離開同步代碼塊後,Thread-0便會察覺到value的值變爲了50,那麼後面的valueAfterSync的值便爲50,最後從break跳出循環,結束Thread-0線程。
意料之外的問題
但實際的執行結果如下:
從結果中可以看出,Thread-0線程並沒有進入while循環,說明Thread-0線程在value的值發生變化後,missedIt的值發生變化前,便察覺到了value值的變化,從而退出了while循環。這與理論上的分析不符,我便嘗試註釋掉value值發生改變與missedIt值發生改變之間的線程休眠代碼Thread.sleep(5000),以確保Thread-0線程在missedIt的值發生改變前,沒有時間察覺到value值的變化。但執行的結果與上面大同小異(可能有一兩行順序不同,但依然不會打印出if語句中的輸出信息)。
問題分析
在JDK1.7~JDK1.3之間的版本上輸出結果與上面基本大同小異,只有在JDK1.2上纔得到了預期的結果,即Thread-0線程中的while循環是從if語句中退出的,這說明Thread-0線程沒有及時察覺到value值的變化。
這裏需要注意:volatile是針對JIT帶來的優化,因此JDK1.2以前的版本基本不用考慮,另外,在JDK1.3.1開始,開始運用HotSpot虛擬機,用來代替JIT。因此,是不是HotSpot的問題呢?這裏需要再補充一點:
JIT或HotSpot編譯器在server模式和client模式編譯不同,server模式爲了使線程運行更快,如果其中一個線程更改了變量boolean flag 的值,那麼另外一個線程會看不到,因爲另外一個線程爲了使得運行更快所以從寄存器或者本地cache中取值,而不是從內存中取值,那麼使用volatile後,就告訴不論是什麼線程,被volatile修飾的變量都要從內存中取值。《內存柵欄》
但看了這個帖子http://segmentfault.com/q/1010000000147713(也有人遇到同樣的問題了)說,嘗試了HotSpot的server和client兩種模式,以及JDK1.3的classic,都沒有效果,只有JDK1.2才能得到預期的結果。
哎!看來自己知識還是比較匱乏,看了下網友給出的答案,對於非volatile修飾的變量,儘管jvm的優化,會導致變量的可見性問題,但這種可見性的問題也只是在短時間內高併發的情況下發生,CPU執行時會很快刷新Cache,一般的情況下很難出現,而且出現這種問題是不可預測的,與jvm, 機器配置環境等都有關。