2014-11-25 47 views
1

說我有一個全局對象:如果只有一個線程寫入和多個線程讀取,是否需要添加一些鎖定或同步?

class Global { 
    public static int remoteNumber = 0; 
} 

有一個線程定期運行從遠程獲取新號碼,並進行更新(只寫):

new Thread { 
    @override 
    public void run() { 
     while(true) { 
      int newNumber = getFromRemote(); 
      Global.remoteNumber = newNumber; 
      Thread.sleep(1000); 
     } 
    } 
} 

而且有一個或多個使用這種全球remoteNumber隨機線程(只讀):

int n = Global.remoteNumber; 
doSomethingWith(n); 

你可以看到我不使用任何鎖或synchronize到PR保護它,這是否正確?有沒有可能導致問題的潛在問題?


更新:

在我而言,它不是真正重要的是,讀線程必須得到實時最新的值。我的意思是,如果有任何問題(導致缺少鎖定/同步),使一個讀取線程錯過該值,這並不重要,因爲它將有機會很快運行相同的代碼(可能在一個循環中)

但不允許讀取未確定的值(我的意思是,如果舊值爲20,新的更新值爲30,但讀取線程讀取的值不是33,我不確定是否可能)

+1

是的,一個線程寫入和多個線程讀取完全是*您需要同步的情況。 – markspace 2014-11-25 07:29:43

+0

對於那個用例,你會遇到可見性問題:讀線程可能看不到全局數字的新值。您至少需要使其變得易變或更好,才能使用AtomicInteger。 – 2014-11-25 07:31:25

+0

閱讀線程可能**從不**看到新的值。爲什麼使用不正確的代碼而不是正確的代碼,特別是當你需要正確的代碼時,使用AtomicInteger或volatile int?你真的認爲這會造成任何性能問題嗎?它不會。 – 2014-11-25 07:39:54

回答

4

這裏您需要同步(有一點需要注意,我將在後面討論)。

主要問題是讀者線程可能永遠不會看到寫入器線程所做的任何更新。通常任何給定的寫入最終都會被看到。但是在這裏你的更新循環非常簡單,寫入操作可以很容易地保存在緩存中,並且永遠不會寫入主內存。所以你真的必須在這裏同步。

編輯11/2017我打算更新這個版本,並說可能長時間在緩存中保留一個值可能不現實。我認爲這是一個問題,儘管像這樣的變量訪問可以通過編譯器進行優化並保存在寄存器中。所以仍需要同步(或volatile)來告訴優化器確保爲每個循環實際獲取新值。

因此,您需要使用volatile,或者您需要使用(靜態)getter和setter方法,並且您需要在兩種方法上使用​​關鍵字。對於偶爾這樣寫,volatile關鍵字是更輕的重量。

需要注意的是,如果您確實不需要從寫入線程看到及時更新,則不必同步。如果無限期延遲不會影響您的程序功能,則可以跳過同步。但是,像定時器這樣的東西看起來不像是一個很好的用例來省略同步。

編輯:根據Brian Goetz的實踐中的Java併發,Java/JVM不允許向您顯示「不確定」值 - 從未寫入的值。這些在技術上更多地稱爲「無法比擬的」值,並且它們被Java規範所禁止。您可以保證看到之前對您的全局變量進行的一些寫操作,或者是初始化爲零的操作,或者是後續的寫操作,但是不允許其他值。

+0

我不知道是否有任何運行的例子可以演示這個問題,而不'volatile或'同步'? – Freewind 2014-11-25 07:50:21

+0

如果你有多個核心,我認爲你可以做到。只有少數核心,進程將被交換到足夠的地步,我不認爲你會注意到更新滯後。但是如果你有更多的內核,我認爲編寫器線程可以保持足夠長的時間以保持足夠的效果。 – markspace 2014-11-25 07:53:12

+0

嗯,如果你有一個易失性字段和一個非易失性字段,你可能可以證明這一點,但你必須小心:同步實際上更新* ALL *寫入Java,所以寫入易失性字段將會更新其他非易失性字段。 (如果你知道那是什麼,那麼Java中的同步基本上是一個內存圍欄,一個用於刷新緩存的機器指令,它會更新所有的寫入。) – markspace 2014-11-25 07:56:27

2

讀線程可以讀取未確定時間的舊值,但實際上沒有問題。因爲每個線程都有自己的這個變量的副本。有時他們同步。您可以使用volatile關鍵字來去除這項優化:

public static volatile int remoteNumber = 0; 
相關問題