[英]Volatile boolean vs AtomicBoolean
易失性 boolean 無法實現的 AtomicBoolean 做了什么?
當所述字段僅由其所有者線程更新並且該值僅由其他線程讀取時,我使用 volatile 字段,您可以將其視為一個發布/訂閱場景,其中有許多觀察者但只有一個發布者。 但是,如果這些觀察者必須根據字段的值執行一些邏輯,然后推回一個新值,那么我會使用 Atomic* 變量或鎖或同步塊,任何最適合我的方法。 在許多並發場景中,它歸結為獲取值,將其與另一個值進行比較並在必要時進行更新,因此存在於 Atomic* 類中的 compareAndSet 和 getAndSet 方法。
檢查java.util.concurrent.atomic包的 JavaDocs 以獲取 Atomic 類列表以及它們如何工作的出色解釋(剛剛了解到它們是無鎖的,因此它們比鎖或同步塊有優勢)
他們只是完全不同。 考慮這個volatile
整數的例子:
volatile int i = 0;
void incIBy5() {
i += 5;
}
如果兩個線程同時調用該函數,之后i
可能是 5,因為編譯后的代碼將與此有點相似(除非您無法在int
同步):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
如果變量是 volatile,則對它的每次原子訪問都是同步的,但實際上什么是原子訪問並不總是很明顯。 使用Atomic*
對象,可以保證每個方法都是“原子的”。
因此,如果您使用AtomicInteger
和getAndAdd(int delta)
,您可以確定結果將為10
。 同樣,如果兩個線程同時否定一個boolean
變量,使用AtomicBoolean
您可以確定它之后具有原始值,使用volatile boolean
,則不能。
因此,每當您有多個線程修改一個字段時,您都需要使其原子化或使用顯式同步。
volatile
的目的是不同的。 考慮這個例子
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
如果您有一個線程運行loop()
和另一個線程調用stop()
,如果您省略volatile
,您可能會遇到無限循環,因為第一個線程可能會緩存 stop 的值。 在這里, volatile
提示編譯器在優化時要更加小心。
您不能使用 volatile 布爾值將compareAndSet
、 getAndSet
作為原子操作(除非您同步它)。
AtomicBoolean
具有以原子方式執行復合操作的方法,而無需使用synchronized
塊。 另一方面, volatile boolean
只能在synchronized
塊中執行復合操作。
讀取/寫入volatile boolean
的記憶效應分別與AtomicBoolean
的get
和set
方法相同。
例如, compareAndSet
方法將自動執行以下操作(沒有synchronized
塊):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
因此, compareAndSet
方法將讓您編寫保證只執行一次的代碼,即使從多個線程調用也是如此。 例如:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
保證只通知偵聽器一次(假設沒有其他線程在將AtomicBoolean
設置為true
后再次將其設置回false
)。
volatile
關鍵字保證共享該變量的線程之間發生先發生的關系。 它不能保證 2 個或更多線程在訪問該布爾變量時不會相互中斷。
揮發性布爾值與原子布爾值
Atomic* 類包裝了一個相同類型的 volatile 原語。 從來源:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
因此,如果您所做的只是獲取和設置 Atomic*,那么您最好只擁有一個 volatile 字段。
AtomicBoolean 做了哪些 volatile boolean 無法實現的事情?
Atomic* 類為您提供了提供更高級功能的方法,例如用於數字的incrementAndGet()
、用於布爾值的compareAndSet()
以及無需鎖定即可實現多個操作(get/increment/set、test/set)的其他方法。 這就是 Atomic* 類如此強大的原因。
例如,如果多個線程使用++
使用以下代碼,則會出現競爭條件,因為++
實際上是:get、increment 和 set。
private volatile value;
...
// race conditions here
value++;
但是,以下代碼將在沒有鎖的情況下安全地在多線程環境中工作:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
同樣重要的是要注意,使用 Atomic* 類包裝 volatile 字段是從對象的角度封裝關鍵共享資源的好方法。 這意味着開發人員不能假設它沒有被共享可能會注入字段 ++ 的問題。 或其他引入競爭條件的代碼。
如果有多個線程訪問類級別變量,則每個線程都可以在其線程本地緩存中保留該變量的副本。
使變量 volatile 將阻止線程在線程本地緩存中保留變量的副本。
原子變量是不同的,它們允許對其值進行原子修改。
布爾基元類型對於寫和讀操作是原子的,易失性保證了先發生原則。 所以如果你需要一個簡單的 get() 和 set() 那么你就不需要 AtomicBoolean。
另一方面,如果您需要在設置變量值之前進行一些檢查,例如“如果為真則設置為假”,那么您也需要以原子方式執行此操作,在這種情況下,請使用 compareAndSet 和其他提供的方法AtomicBoolean,因為如果您嘗試使用 volatile boolean 實現此邏輯,您將需要一些同步以確保值在 get 和 set 之間沒有更改。
如果你只有一個線程修改你的布爾值,你可以使用一個 volatile 布爾值(通常你這樣做是為了定義一個在線程的主循環中檢查的stop
變量)。
但是,如果您有多個線程修改布爾值,則應使用AtomicBoolean
。 否則,以下代碼是不安全的:
boolean r = !myVolatileBoolean;
此操作分兩步完成:
如果其他線程修改了#1
和2#
之間的值,您可能會得到錯誤的結果。 AtomicBoolean
方法通過原子地執行步驟#1
和#2
來避免這個問題。
記住成語 -
讀取 - 修改 - 寫入這是您無法使用 volatile 實現的
這里的很多答案都過於復雜、令人困惑或只是錯誤的。 例如:
…如果您有多個線程修改 boolean,您應該使用
AtomicBoolean
。
作為一般性陳述,這是不正確的。
如果一個變量是 volatile 的,對它的每個原子訪問都是同步的……
這是不正確的; 同步完全是另一回事。
簡單的答案是AtomicBoolean
允許您在某些需要讀取值然后寫入取決於您讀取的內容的值的操作中防止競爭條件; 它使此類操作具有原子性(即,它消除了變量可能在讀取和寫入之間發生變化的競爭條件)——因此得名。
如果您只是在讀取和寫入不依賴於您剛剛讀取的值的變量,那么即使使用多個線程, volatile
也可以正常工作。
兩者的概念相同,但在原子布爾值中,如果 CPU 切換發生在兩者之間,它將為操作提供原子性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.