2011-04-28 38 views
13

目前我無法理解何時應該使用volatile來聲明變量。應該使用可以證明「易失性」聲明的代碼示例

我已經做了一些研究,並且搜索了很長時間的一些資料,並且知道當一個字段被聲明爲volatile時,編譯器和運行時會注意到這個變量是共享的,並且它的操作不應該是與其他內存操作重新排序。

但是,我仍然不明白在什麼情況下我們應該使用它。我的意思是可以有人提供任何示例代碼,可以證明使用「易失性」帶來好處或解決問題相比,而不使用它?

+6

'volatile'(和許多其他併發相關方面)都非常** **硬展示, 因爲如果您錯誤地使用它們,那麼它們*可能*仍然顯示正常工作,並且在出現非常特定的情況並且導致問題之前不會顯示問題。 – 2011-04-28 10:05:40

+0

我已經嘗試過這個,並且生成了一個測試,它在成功使用非易失性場的線程之間傳遞值,但是在加載相同的方框時會相對快速地失敗。即使計時易變性對績效的影響也非常困難,這取決於很多因素。 – 2011-04-28 10:24:38

回答

12

以下是爲什麼volatile是必要的示例。如果您刪除關鍵字volatile,線程1 可能永不終止。 (當我在Java 1.6的熱點在Linux上測試,這是事實確實如此 - 因爲JVM是沒有義務這樣做沒有標明volatile變量任何緩存您的結果可能會有所不同。)

public class ThreadTest { 
    volatile boolean running = true; 

    public void test() { 
    new Thread(new Runnable() { 
     public void run() { 
     int counter = 0; 
     while (running) { 
      counter++; 
     } 
     System.out.println("Thread 1 finished. Counted up to " + counter); 
     } 
    }).start(); 
    new Thread(new Runnable() { 
     public void run() { 
     // Sleep for a bit so that thread 1 has a chance to start 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException ignored) { } 
     System.out.println("Thread 2 finishing"); 
     running = false; 
     } 
    }).start(); 
    } 

    public static void main(String[] args) { 
    new ThreadTest().test(); 
    } 
} 
+0

對不起,我試圖刪除「volatile」關鍵字並多次運行該程序,但每次線程1都可以終止。我認爲即使沒有「易失性」線程1,您提供的代碼也可以隨時終止。 – 2011-04-28 10:40:11

+3

@Eric:它給出了我在兩臺運行Java 6 JVM的不同機器上描述的效果。我想我應該用不同的措辭。只有當volatile關鍵字存在時才能保證終止。如果它不存在,JVM可以自由緩存'running'的值,所以線程1不需要終止。但是,它不會被強制緩存該值。 – 2011-04-28 11:25:51

+0

只要線程2中的「運行」設置爲false,保持「易失性」將立即終止線程1.但是,如果刪除「易失性」,即使在線程2中將「運行」設置爲false之後,線程1也不會終止,將終止取決於JVM何時在線程1中更新「運行」值。你的意思是?但是如果線程2中的「running」被設置爲「false」,爲什麼thread1不能獲得正確的值?如果是的話,我認爲JVM的行爲是錯誤的。 – 2011-04-28 11:53:30

1

volatile關鍵字非常複雜,您需要了解它的功能,並且在使用它之前做得並不好。我建議閱讀this language specification section,這很好地解釋它。

他們強調這個例子:

class Test { 
    static volatile int i = 0, j = 0; 
    static void one() { i++; j++; } 
    static void two() { 
     System.out.println("i=" + i + " j=" + j); 
    } 
} 

這意味着是,在one()j永遠不會比i更大。但是,運行two()的另一個線程可能會打印出比i大得多的值j,因爲我們假設two()正在運行並獲取值i。然後one()運行1000次。然後,運行兩個線程的線程終於再次被安排並獲取j,該值現在比值i大得多。我認爲這個例子完全證明了volatile和synchronized之間的區別 - 對ij的更新是不穩定的,這意味着它們發生的順序與源代碼一致。然而,這兩個更新單獨發生,而不是自動發生,因此調用者可能會看到(對該調用者)看起來不一致的值。

總而言之:要小心volatile

+0

從您提供的鏈接中,可以看到下面的一個例子:class Test { \t static volatile int i = 0,j = 0; \t static void one(){i ++; J ++; } \t static void two(){ \t \t System.out.println(「i =」+ i +「j =」+ j);} \t} } 「這允許方法一和方法二同時執行,但保證訪問i和j的共享值的次數完全相同,並且順序完全相同,因爲它們看起來像在發生每個線程執行程序文本「但是我不認爲這個例子是正確的,使用」volatile「或者不具有相同的結果,j總是有機會與我不一樣 – 2011-04-28 10:50:05

+0

@Eric Jiang - 關鍵是j雖然我總是先遞增,但如果兩者都是不穩定的,他們仍然可能不同,但是j不能大於我。 – Ishtar 2011-04-28 11:43:12

+0

我已經更新了我的答案,以解釋我認爲JLS在說什麼(儘管我認爲JLS在第一名的時候說得很好):) – alpian 2011-04-28 12:00:09

2

埃裏克,我已閱讀您的意見和一個特別是我罷工

其實,我可以理解在概念 級別上的易失性的用法。但實踐中,我想不出 了具有併發 問題的代碼,而無需由西蒙·尼克森提到使用揮發性

明顯的問題,你可以有是編譯器重新排序,例如比較有名的吊裝。但讓我們假設不會有重新排序,那個評論可能是一個有效的評論。

另一個問題,即易失性解析與64位變量(長,雙)。如果您寫入長整數或雙整數,則將其視爲兩個獨立的32位存儲。併發寫入會發生什麼情況是一個線程的高32被寫入寄存器的高32位,而另一個線程寫低32位。那麼你可以有一個很長的,既不是一個,也不是另一個。另外,如果你看看JLS的內存部分,你會發現它是一個輕鬆的內存模型。

這意味着一段時間寫入操作可能不會變爲可見(可以放在存儲緩衝區中)。這可能導致陳舊的讀取。現在你可能會說這看起來不太可能,但是你的程序是不正確的,有可能失敗。

如果你在應用程序的生命週期中增加了一個int,並且你知道(或者至少以爲)int不會溢出,那麼你不會將它升級到很長的時間,但它仍然是可能的。在內存可見性問題的情況下,如果您認爲它不應該對您產生影響,您應該知道它仍然可以並且可能會導致難以識別的併發應用程序中的錯誤。正確性是使用volatile的原因。

3

以下是易失性的必要性(在這種情況下,用於str變量的典型的例子,沒有它,熱點提起環路(while (str == null)外部訪問)和run()從不終止。這將發生在最-server的JVM 。

public class DelayWrite implements Runnable { 
    private String str; 
    void setStr(String str) {this.str = str;} 

    public void run() { 
      while (str == null); 
      System.out.println(str); 
    } 

    public static void main(String[] args) { 
      DelayWrite delay = new DelayWrite(); 
      new Thread(delay).start(); 
      Thread.sleep(1000); 
      delay.setStr("Hello world!!"); 
    } 
} 
+0

Wesley:非常感謝!我可以看到java -server的問題來啓動JVM並運行應用程序。現在我得到我的答案,謝謝! – 2011-04-29 03:20:46

+0

new Thread(fun).start();這一行應該是新的Thread(delay).start();正確嗎? – 2015-02-04 09:00:16

+0

是的,固定的。謝謝。 – 2015-02-04 23:26:15

0

在Java 8簡約的例子,如果你刪除volatile關鍵字就永遠不會結束。

public class VolatileExample { 

    private static volatile boolean BOOL = true; 

    public static void main(String[] args) throws InterruptedException { 
     new Thread(() -> { while (BOOL) { } }).start(); 
     TimeUnit.MILLISECONDS.sleep(500); 
     BOOL = false; 
    } 
}