2013-04-27 74 views
31

在讀取有關內存一致性錯誤的Java文檔時。我發現有關創建發生的兩個動作點 - 關係之前:內存一致性 - 在Java中發生之前的關係

  • 當語句調用Thread.start(),每一個具有 聲明之前發生這種說法的關係也有一個 之前發生與每一個關係語句由新的 線程執行。新線程可以看到導致創建新線程的代碼的影響。

  • 當一個線程終止,並導致在另一個線程 一個Thread.join()返回,那麼所有被終止
    線程執行的語句有之前發生的所有成功加入語句
    以下關係。代碼對線程 的影響現在對執行連接的線程可見。

我無法理解它們的含義。如果有人用一個簡單的例子來解釋它,那將會很棒。

+5

'發生之前關係'意味着這些語句集合保證在另一組語句之前執行。因此,在第一種情況下,導致啓動新線程的語句與新啓動的線程將執行的語句之間具有發生之前的關係。這些語句所做的任何更改都將對線程執行的語句可見。 – 2013-04-27 06:23:11

+1

我覺得這個網頁很有用:http://preshing.com/20130702/the-happens-before-relation/它給出了A和B之間「發生之前」關係與B之前實際發生的A不同的例子。 – 2015-02-11 17:40:19

回答

26

現代CPU並不總是按照更新的順序將數據寫入內存,例如,如果運行僞代碼(假設變量總是存儲在內存中以方便操作);

a = 1 
b = a + 1 

...的CPU很可能寫b內存寫入a內存之前。只要你在一個單獨的線程中運行,這不是一個真正的問題,因爲運行上面代碼的線程一旦完成賦值就永遠不會看到任何一個變量的舊值。多線程是另一回事,你會認爲下面的代碼會讓另一個線程拿起你沉重的計算的價值;

a = heavy_computation() 
b = DONE 

...其他線程做...

repeat while b != DONE 
    nothing 

result = a 

雖然是在完成標誌可以在內存中的結果之前設置的問題是存儲到內存中,因此其他線程在計算結果被寫入內存之前可能會讀取內存地址a的值。

同樣的問題會 - 如果Thread.startThread.join沒有保證「之前發生」 - 給你喜歡代碼的問題;

a = 1 
Thread.start newthread 
... 

newthread: 
    do_computation(a) 

...因爲a在線程啓動時可能沒有存儲到內存的值。

因爲你幾乎總是需要新的線程能夠使用您在開始之前,初始化數據,Thread.start有保證了「之前發生」,即,具有呼叫Thread.start之前被更新保證數據可用到新線程。同樣的事情發生在Thread.join,其中由新線程寫入的數據保證對終止後加入它的線程可見。

它只是使線程更容易。

7

線程可見性問題可能發生在根據Java內存模型未正確同步的代碼中。由於編譯器&硬件優化,一個線程的寫入並不總是可見的另一個線程的讀取。 Java內存模型是一種使「正確同步」規則清晰的正式模型,以便程序員可以避免線程可見性問題。

發生在之前的是在該模型中定義的關係,它指的是特定的執行。被證明爲的寫入W發生在之前發生,讀取R被保證可以被該讀取看到,假設沒有其他干擾寫入(即沒有發生 - 在與讀取之間的關係之前或者在它們之間發生之一根據那個關係)。

最簡單的一種發生之前的關係發生在同一線程中的動作之間。假設W根據程序順序在R之前出現,在線程P中的V寫入W發生在同一線程中的V的讀取R之前。

您提到的文本指出thread.start()和thread.join()也保證了發生之前的關係。在thread.start()之前發生的任何動作也發生在該線程內的任何動作之前。同樣,線程內的動作發生在thread.join()之後出現的任何動作之前。

那是什麼意思?例如,如果您啓動一個線程並等待它以非安全方式終止(例如,長時間休眠或測試某些非同步標誌),那麼當您嘗試讀取數據修改線程,你可能會看到它們的一部分,從而存在數據不一致的風險。 join()方法充當了一個屏障,可以保證線程發佈的任何數據片段都可以被其他線程完全一致地看到。

18

考慮一下:

static int x = 0; 

public static void main(String[] args) { 
    x = 1; 
    Thread t = new Thread() { 
     public void run() { 
      int y = x; 
     }; 
    }; 
    t.start(); 
} 

主線程已經改變場x。 Java內存模型不保證如果其他線程未與主線程同步,則此更改將可見。但線程t將看到此更改,因爲名爲t.start()的主線程和JLS保證呼叫t.start()使更改爲在t.run()中可見,因此y保證被分配爲1

了同樣的擔憂Thread.join();

+0

嗯,我同意,稍微改變了答案,以避免critisizm,現在看看它 – 2013-04-27 07:03:33

1

據Oracle文檔,它們定義之前發生關係是一個簡單的保證內存由一個特定的聲明寫道:可見到另一個具體聲明。

package happen.before; 

public class HappenBeforeRelationship { 


    private static int counter = 0; 

    private static void threadPrintMessage(String msg){ 
     System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg); 
    } 

    public static void main(String[] args) { 

     threadPrintMessage("Increase counter: " + ++counter); 
     Thread t = new Thread(new CounterRunnable()); 
     t.start(); 
     try { 
      t.join(); 
     } catch (InterruptedException e) { 
      threadPrintMessage("Counter is interrupted"); 
     } 
     threadPrintMessage("Finish count: " + counter); 
    } 

    private static class CounterRunnable implements Runnable { 

     @Override 
     public void run() { 
      threadPrintMessage("start count: " + counter); 
      counter++; 
      threadPrintMessage("stop count: " + counter); 
     } 

    } 
} 

輸出將是:

[Thread main] Increase counter: 1 
[Thread Thread-0] start count: 1 
[Thread Thread-0] stop count: 2 
[Thread main] Finish count: 2 

看一看輸出,線[線程的線程0]開始計數:1表明Thread.start的調用之前(所有計數器的變化)是在Thread的主體中可見。

和行[Thread main]完成計數:2指示調用Thread.join()的主線程可以看到Thread的正文中的所有更改。

希望它能幫助你清楚。

相關問題