2011-08-01 37 views
8

如果我有一個變量,多個線程讀取並且只有一個線程寫入,那麼是否需要鎖定該變量?如果一個線程嘗試讀取並且另一個線程嘗試同時寫入,它會崩潰嗎?Java中併發讀取/寫入變量

回答

7

併發擔憂並不崩潰,但你看到的版本的數據。

  • 如果共享變量原子寫的,這是可能的,因爲當你認爲你的(作家)線程已經更新了一個變量(讀者)線程讀取一個不完整的數值。您可以使用易變關鍵字來防止讀者線程在這種情況下讀取過時的值。

  • 如果寫入操作不是原子的(例如,如果它是某種組合對象,並且您一次只寫一些數據,而其他線程理論上可以讀取它),那麼您的擔心也會有些讀者線程可能會看到處於不一致狀態的變量。您可以通過在寫入時緩存對變量的訪問(緩慢)或確保您正在以原子方式寫入來防止這種情況。

3

請注意,volatile不是原子的,這意味着使用64位的double和long可以在32位爲舊值且32位爲新值的不一致狀態下讀取。此外,易失性陣列不會使陣列條目變得不穩定。強烈建議使用java.util.concurrent中的類。

+1

我不知道,雙打和多頭可以處於不一致的狀態可以看出。那麼這是否也適用於64位系統上的(64位)引用,並且意味着您可以獲得無效的引用? –

+0

由於解鎖寫入導致的不一致的長整數和雙整數通常被稱爲「長期撕裂」。在64位jvm中由64位指針支持的對象引用不受此問題的影響。也就是說,對象引用的「寫入」總是原子的。 –

+4

易變「長」和「雙」值**是原子**,與非易失性「長」和「雙」值(可能不是原子)相反。即使非易失性,其他原始值也是原子。 –

4

簡單的答案是肯定的,你需要同步。

如果你寫一個領域,如果沒有某種形式的同步,從其他地方讀它,你的程序可以看到不一致的狀態,很可能是錯誤的。你的程序不會崩潰,但可以看到舊的或新的或者(在長時間和雙打的情況下)半個新舊數據。

雖然我說「某種形式的同步」,但我更確切地說是指在寫入和讀取位置之間創建「發生之前」關係(又名存儲障礙)的東西。同步或java.util.concurrent.lock類是創建此類事物最明顯的方式,但所有併發集合通常也提供類似的保證(檢查javadoc以確保)。例如,執行put和take一個併發隊列將創建一個before-before關係。

標記字段揮發性阻止你看到不一致的引用(長撕裂),並保證所有的線程將「看」的寫操作。但是,易失性字段寫入/讀取不能與更大原子單元中的其他操作組合。 Atomic類處理常見的組合操作,如比較和設置或讀取和增量。同步或其他java.util.concurrent同步器(CyclicBarrier等)或鎖應該用於更大的排他性領域。

從簡單的是開車的,也有更多的「不,如果你真的知道你在做什麼」的情況。舉兩個例子:

1)一個字段是最後的也是唯一的建設過程中寫入的特殊情況。其中一個例子是,當您填充預先計算的緩存時(想到一個Map,其中鍵是衆所周知的值,值是預先計算的派生值)。如果在構建之前將其構建到字段中,並且該字段是最終的,並且以後再也不寫入,則構造函數的結尾將執行「最終字段凍結」,並且後續讀取不需要同步。

2)被覆蓋在有效的Java的「活潑單個檢查」圖案的情況下。規範示例在java.lang.String中。hashCode()方法。首次調用hashCode()並將其緩存到未同步的本地字段中時,字符串的哈希字段將被延遲計算。基本上,多個線程可以競爭來計算這個值並且設置在其他線程上,但是因爲它被一個知名的哨兵(0)保護並且總是計算相同的值(所以我們不關心哪個線程「獲勝」或者是否多重做),這實際上是保證沒問題。

更長的參考(我寫的):http://refcardz.dzone.com/refcardz/core-java-concurrency