揮発性ブール値では達成できないAtomicBooleanの機能
それらはまったく異なります。 volatile
整数のこの例を考えてみましょう。
volatile int i = 0;
void incIBy5() {
i += 5;
}
2つのスレッドが同時に関数を呼び出す場合、コンパイルされたコードはこれに多少似ているため、i
はその後5になります(int
で同期できないことを除く)。
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()
を呼び出す別のスレッドがある場合、volatile
を省略すると、最初のスレッドがstopの値をキャッシュするため、無限ループに陥る可能性があります。ここで、volatile
は、コンパイラーが最適化にもう少し注意を払うためのヒントとして機能します。
上記のフィールドが所有者スレッドによってのみ更新され、値が他のスレッドによってのみ読み取られる場合、揮発性フィールドを使用します。これは、多くのオブザーバーが存在するがパブリッシャーが1つだけのパブリッシュ/サブスクライブシナリオと考えることができます。ただし、これらのオブザーバーがフィールドの値に基づいて何らかのロジックを実行し、新しい値をプッシュバックする必要がある場合は、Atomic * varsまたはロックまたは同期ブロックを使用します。多くの並行シナリオでは、値を取得して別の値と比較し、必要に応じて更新するために要約されます。したがって、Atomic *クラスに存在するcompareAndSetおよびgetAndSetメソッドです。
Java.util.concurrent.atomic パッケージのJavaDocsをチェックして、Atomicクラスのリストとそれらがどのように機能するかについての優れた説明を確認してくださいロックまたは同期ブロック)
compareAndSet
、getAndSet
をvolatileブール値を使用したアトミック操作として実行することはできません(もちろん同期しない限り)。
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();
}
リスナーに一度だけ通知することが保証されます(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 *を取得して設定するだけの場合は、代わりにvolatileフィールドを使用することもできます。
揮発性ブール値では達成できないAtomicBooleanの機能
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は必要ありません。
一方、変数の値を設定する前にいくつかのチェックを実装する必要がある場合は、 「trueの場合falseに設定する」場合、この操作もアトミックに行う必要があります。この場合、compomicAndSetとAtomicBooleanが提供する他のメソッドを使用します。 getとsetの間で値が変更されていないことを確認してください。
IDIOMを覚えておいてください-
READ-MODIFY- WRITEこれはvolatileでは達成できません
ブール値を変更するスレッドが1つしかない場合は、揮発性ブール値を使用できます(通常、これを実行して、スレッドのメインループでチェックされるstop
変数を定義します)。
ただし、ブール値を変更する複数のスレッドがある場合は、AtomicBoolean
を使用する必要があります。そうでない場合、次のコードは安全ではありません。
boolean r = !myVolatileBoolean;
この操作は2つのステップで実行されます。
他のスレッドが#1
と2#
の間の値を変更すると、間違った結果になる可能性があります。 AtomicBoolean
メソッドは、ステップ#1
および#2
をアトミックに実行することにより、この問題を回避します。