揮発性ブール値では達成できないAtomicBooleanは何をしますか?
11 に答える
揮発性フィールドは、そのフィールドが所有者スレッドによってのみ更新され、値が他のスレッドによってのみ読み取られる場合に使用します。これは、オブザーバーが多く、パブリッシャーが1つしかないパブリッシュ/サブスクライブシナリオと考えることができます。ただし、これらのオブザーバーがフィールドの値に基づいてロジックを実行してから新しい値をプッシュバックする必要がある場合は、Atomic *変数、ロック、または同期ブロックを使用します。多くの並行シナリオでは、値を取得し、それを別の値と比較し、必要に応じて更新するため、Atomic*クラスに存在するcompareAndSetメソッドとgetAndSetメソッドに要約されます。
java.util.concurrent.atomicパッケージのJavaDocsで、Atomicクラスのリストと、それらがどのように機能するかについての優れた説明を確認してください(ロックフリーであることがわかったため、ロックや同期ブロックよりも優れています)
それらはまったく異なります。volatile
この整数の例を考えてみましょう。
volatile int i = 0;
void incIBy5() {
i += 5;
}
2つのスレッドが同時に関数を呼び出す場合i
、コンパイルされたコードはこれにいくらか似ているため(で同期できないことを除いてint
) 、後で5になる可能性があります。
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
変数が揮発性である場合、その変数へのすべてのアトミックアクセスは同期されますが、実際にアトミックアクセスとして適格であるかどうかは必ずしも明らかではありません。オブジェクトを使用Atomic*
すると、すべてのメソッドが「アトミック」であることが保証されます。
したがって、とを使用するAtomicInteger
とgetAndAdd(int delta)
、結果が。になることを確認できます10
。同様に、2つのスレッドが両方ともboolean
変数を同時に否定するAtomicBoolean
場合、後で元の値を持っていることを確認できますが、を使用するvolatile boolean
とできません。
したがって、フィールドを変更するスレッドが複数ある場合は常に、フィールドをアトミックにするか、明示的な同期を使用する必要があります。
目的はvolatile
別です。この例を考えてみましょう
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
実行中のスレッドと、をloop()
呼び出す別のスレッドがある場合、最初のスレッドがstopの値をキャッシュする可能性があるため、stop()
を省略すると無限ループに陥る可能性があります。volatile
ここで、は、volatile
最適化にもう少し注意するためのコンパイラへのヒントとして機能します。
compareAndSet
揮発性ブール値を使用した不可分操作としては実行できませんgetAndSet
(もちろん同期しない限り)。
AtomicBoolean
synchronized
ブロックを使用せずに、複合演算をアトミックに実行するメソッドがあります。一方、複合操作は、ブロックvolatile boolean
内で実行した場合にのみ実行できます。synchronized
の読み取り/書き込みのメモリ効果は、それぞれのおよびメソッドとvolatile boolean
同じです。get
set
AtomicBoolean
たとえば、このcompareAndSet
メソッドは次のことをアトミックに実行します(synchronized
ブロックなし)。
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
したがって、このcompareAndSet
メソッドを使用すると、複数のスレッドから呼び出された場合でも、1回だけ実行されることが保証されているコードを記述できます。例えば:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
リスナーに通知するのは1回だけであることが保証されています(に設定された後、他のスレッドが再びにAtomicBoolean
戻ることはないと想定しています)。false
true
volatile
キーワードは、その変数を共有するスレッド間の関係の前に発生することを保証します。そのブール変数にアクセスしている間、2つ以上のスレッドが互いに割り込まないことを保証するものではありません。
揮発性ブール値とAtomicBoolean
Atomic *クラスは、同じタイプの揮発性プリミティブをラップします。ソースから:
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 *を取得して設定するだけの場合は、代わりに揮発性フィールドを使用することをお勧めします。
揮発性ブール値では達成できないAtomicBooleanは何をしますか?
incrementAndGet()
Atomic *クラスは、数値、compareAndSet()
ブール値、およびロックせずに複数の操作(get / incremental / set、test / set)を実装するその他のメソッドなどのより高度な機能を提供するメソッドを提供します。そのため、Atomic*クラスは非常に強力です。
たとえば、複数のスレッドがを使用して次のコードを使用している場合、実際にはget、increment、setである++
ため、競合状態が発生します。++
private volatile value;
...
// race conditions here
value++;
ただし、次のコードは、ロックなしでマルチスレッド環境で安全に機能します。
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
また、Atomic *クラスを使用して揮発性フィールドをラップすることは、オブジェクトの観点から重要な共有リソースをカプセル化するための良い方法であることに注意することも重要です。つまり、開発者は、フィールドが共有されていないことを前提としてフィールドを処理することはできず、フィールド++に問題が発生する可能性があります。または競合状態を導入するその他のコード。
クラスレベルの変数にアクセスする複数のスレッドがある場合、各スレッドはその変数のコピーをスレッドローカルキャッシュに保持できます。
変数を揮発性にすると、スレッドが変数のコピーをスレッドローカルキャッシュに保持できなくなります。
アトミック変数は異なり、値をアトミックに変更できます。
IDIOMを覚えておいてください-
読み取り-変更-揮発性では達成できないこれを書き込みます
ブールプリミティブ型は書き込みおよび読み取り操作ではアトミックであり、volatileは発生前の原則を保証します。したがって、単純なget()とset()が必要な場合は、AtomicBooleanは必要ありません。
一方、変数の値を設定する前にチェックを実装する必要がある場合(たとえば、「trueの場合はfalseに設定」)、この操作もアトミックに実行する必要があります。この場合は、compareAndSetおよびによって提供される他のメソッドを使用します。 AtomicBoolean、揮発性ブール値を使用してこのロジックを実装しようとすると、getとsetの間で値が変更されていないことを確認するために同期が必要になるためです。
ブール値を変更するスレッドが1つしかない場合は、揮発性ブール値を使用できます(通常、これをstop
行って、スレッドのメインループでチェックされる変数を定義します)。
ただし、ブール値を変更する複数のスレッドがある場合は、を使用する必要がありますAtomicBoolean
。それ以外の場合、次のコードは安全ではありません。
boolean r = !myVolatileBoolean;
この操作は2つのステップで実行されます。
- ブール値が読み取られます。
- ブール値が書き込まれます。
他のスレッドがとの間の値を変更する#1
と2#
、間違った結果が得られる可能性があります。AtomicBoolean
メソッドは、ステップを実行#1
して#2
アトミックにこの問題を回避します。
どちらも同じ概念ですが、アトミックブールでは、CPUスイッチが間に発生した場合に操作にアトミック性を提供します。