簡體   English   中英

Java:將所有字段設為final還是volatile?

[英]Java: Make all fields either final or volatile?

如果我有一個在線程之間共享的對象,在我看來,每個字段應該是finalvolatile ,具有以下推理:

  • 如果應該更改字段(指向另一個對象,更新原始值),則該字段應該是volatile以便所有其他線程對新值進行操作。 僅僅訪問所述字段的方法的同步是不夠的,因為它們可能返回緩存的值。

  • 如果該領域永遠不會改變,那就讓它成為final

但是,我找不到任何關於此的內容,所以我想知道這個邏輯是否有缺陷還是太明顯?

當然編輯而不是volatile可能會使用final AtomicReference或類似的。

編輯 ,例如,請參閱get getter方法是Java中volatile的替代方法嗎?

編輯以避免混淆: 這個問題是關於緩存失效! 如果兩個線程對同一個對象進行操作,則可以緩存對象的字段(每個線程),如果它們未聲明為volatile。 如何保證緩存無效?

最后的編輯感謝@Peter Lawrey,他指出了JLS§17(Java內存模型)。 據我所知,它表明同步在操作之間建立了先發生關系,因此如果那些更新“發生在之前”,則線程會看到來自另一個線程的更新,例如,如果非易失性字段的getter和setter是synchronized

雖然我認為private final應該是字段和變量的默認值,其中包含var這樣的關鍵字使其變得可變,但是當你不需要它時使用volatile是

  • 慢得多,通常慢10倍左右。
  • 通常不會為您提供所需的線程安全性,但可以通過使它們不太可能出現而更難發現這些錯誤。
  • 不像final那樣通過說不應該改變它來提高清晰度,在不需要時使用volatile ,可能會讓讀者試圖弄清楚為什么它變得不穩定會讓人感到困惑。

如果應該更改字段(指向另一個對象,更新原始值),則該字段應該是volatile,以便所有其他線程對新值進行操作。

雖然這對於讀取是好的,但請考慮這個簡單的情況。

volatile int x;

x++;

這不是線程安全的。 因為它是一樣的

int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;

更糟糕的是,使用volatile會使這種bug更難找到。

正如yshavit指出的那樣,更新多個字段更難以解決volatile例如HashMap.put(a, b)更新多個引用。

僅僅訪問所述字段的方法的同步是不夠的,因為它們可能返回緩存的值。

synchronized給你所有內存保證的volatile和更多,這就是它明顯變慢的原因。

注意:只是synchronized每個方法並不總是足夠的。 StringBuffer使每個方法都同步,但在多線程上下文中是無用的,因為它的使用很可能容易出錯。

很容易認為實現線程安全就像灑上仙塵一樣,添加一些神奇的線程安全,你的bug就會消失。 問題是螺紋安全更像是一個有很多孔的鏟斗。 插入最大的孔並且這些漏洞看起來會消失,但除非你全部插上它們,否則你沒有線程安全,但它可能更難找到。

就同​​步與易失性而言,這表明了這一點

其他機制,例如volatile變量的讀寫和java.util.concurrent包中的類的使用,提供了替代的同步方法。

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

無論線程問題如何,創建不需要更改final字段都是一個好主意。 它使類的實例更容易推理,因為你可以更容易地知道它的狀態。

在使其他字段volatile

僅僅訪問所述字段的方法的同步是不夠的,因為它們可能返回緩存的值。

如果訪問synchronized塊之外的值,則只能看到緩存的值。

所有訪問都需要正確同步。 保證在另一個同步塊的啟動之前(在同一監視器上同步時)發生一個同步塊的結束。

至少有幾種情況你仍然需要使用同步:

  • 如果必須以原子方式讀取並更新一個或多個字段,則需要使用同步。
    • 您可以避免某些單個字段更新的同步,例如,如果您可以使用Atomic*類而不是“普通舊字段”; 但即使對於單個字段更新,您仍然可能需要獨占訪問權限(例如,將一個元素添加到列表中,同時刪除另一個元素)。
  • 此外,volatile / final可能不足以用於非線程安全值,如ArrayList或數組。

如果線程之間共享一個對象,則有兩個明確的選項:

1.將該對象設為只讀

因此,更新(或緩存)沒有任何影響。

2.同步對象本身

緩存失效很難。 很難。 因此,如果您需要保證沒有陳舊的值,您應該保護該值並保護圍繞所述值的鎖定

在共享對象上將鎖和值設置為私有,因此這里的操作是實現細節。

為避免死鎖,此操作應為“原子”,以避免與其他任何鎖相互作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM