『Effective Java book』には、次のように記載されています。
long
言語仕様では、変数の型またはdouble
[JLS、17.4.7]でない限り、変数の読み取りまたは書き込みがアトミックであることを保証しています。
Javaプログラミング、またはプログラミング全般のコンテキストで「アトミック」とはどういう意味ですか?
例を次に示します:foo
が type の変数であると仮定するとlong
、次の操作はアトミック操作ではありません ( Javaの場合):
foo = 65465498L;
実際、変数は 2 つの別個の操作を使用して書き込まれます。最初の 32 ビットを書き込む操作と、最後の 32 ビットを書き込む操作です。つまり、別のスレッドが の値を読み取りfoo
、中間状態を確認する可能性があります。
操作をアトミックにするには、同期メカニズムを使用して、操作が他のスレッドから単一のアトミック (つまり、部分的に分割できない) 操作として認識されるようにする必要があります。つまり、他のスレッドは、操作がアトミックになるとfoo
、割り当て前または割り当て後のいずれかの値を参照します。しかし、決して中間値ではありません。
これを行う簡単な方法は、変数を volatileにすることです。
private volatile long foo;
または、変数へのすべてのアクセスを同期するには:
public synchronized void setFoo(long value) {
this.foo = value;
}
public synchronized long getFoo() {
return this.foo;
}
// no other use of foo outside of these two methods, unless also synchronized
または、次のように置き換えますAtomicLong
。
private AtomicLong foo;
「アトミック操作」とは、他のすべてのスレッドの観点から瞬時に見える操作を意味します。保証が適用される場合、部分的に完全な操作について心配する必要はありません。
それは「システムの残りの部分には瞬時に発生するように見える」ものであり、計算プロセスにおける線形化可能性のカテゴリに分類されます。そのリンクされた記事をさらに引用するには:
原子性は、並行プロセスからの分離を保証します。さらに、アトミック操作には通常、成功または失敗の定義があります。システムの状態を正常に変更するか、明らかな影響を与えないかのいずれかです。
したがって、たとえば、データベース システムのコンテキストでは、「アトミック コミット」を持つことができます。つまり、更新の変更セットをリレーショナル データベースにプッシュすると、それらの変更がすべてサブミットされるか、まったくサブミットされないかのいずれかになります。障害が発生した場合、このようにしてデータが破損せず、ロックやキューの結果として、次の操作は別の書き込みまたは読み取りになりますが、事後のみです。変数とスレッドのコンテキストでは、これはほとんど同じで、メモリに適用されます。
あなたの引用は、これがすべての場合に予想される動作である必要はないことを強調しています。
以下のコードでメソッド m1 および m2 を実行する複数のスレッドがある場合:
class SomeClass {
private int i = 0;
public void m1() { i = 5; }
public int m2() { return i; }
}
m2
スレッド呼び出しが 0 または 5 を読み取ることが保証されます。
一方、このコード (i
は long ):
class SomeClass {
private long i = 0;
public void m1() { i = 1234567890L; }
public long m2() { return i; }
}
呼び出しているスレッドm2
は、0、1234567890L、またはその他のランダムな値を読み取る可能性があります。これは、ステートメントi = 1234567890L
がアトミックであることが保証されていないためですlong
(JVM は最初の 32 ビットと最後の 32 ビットを 2 つの操作で書き込みi
、その間にスレッドが観察する可能性があります)。 .