プリミティブ型の場合は揮発性か同期か?
-
21-09-2019 - |
質問
Java では、変数のサイズが 32 ビット以下の場合、代入はアトミックですが、32 ビットを超える場合はアトミックではありません。
二重割り当てまたは長い割り当ての場合、どちら (揮発性/同期) を使用するのがより効率的ですか?
のように、
volatile double x = y;
synchronized はプリミティブ引数には適用できません。この場合、同期を使用するにはどうすればよいですか?もちろんクラスをロックしたくないので、 this
使用すべきではありません。
解決
あなたはあまりにも重いオブジェクト自体にロックを見つける場合は、同期が進むべき道です。揮発性のJava 1.5より前では良い選択だったかもしれないが、今揮発性は、割り当てが起こる方法に命令の順序を強制することにより、非常に大きな影響を与えることがあります。その二重の値を設定または取得するとき、その上に別のオブジェクト(private final Object X_LOCK = new Object();
)と同期を作成します。これはあなたにあなたが必要と思われるロック、オーバー制御の細かいレベルを提供します。
新しい並行処理パッケージでは、あなたが本当に回避同期する必要がある場合は揮発性のために良い代替することができるようAtomicReferenceなど、より多くのオプションが、あります。
他のヒント
あなたは何をしようとしているのですか?の synchronized
そして volatile
キーワードは、同じデータを読み取るさまざまなスレッドで一貫した値が確実に観察されるようにするために使用できる Java のメカニズムです。特に、以下について推論することができます。 前に起こる プログラム内の関係。
次のいずれかの使用を避けることはできません。 volatile
または synchronized
非アクセスに適切にアクセスするには、final
マルチスレッド プログラムのフィールド。そうは言っても、おそらく必要になる主な理由は、 synchronized
以上 volatile
アトミックを使用するための要件です 比較して設定する 操作(すなわち、 それはパフォーマンスを考慮するものではありません)。たとえば、マルチスレッド プログラムでは次のようになります。
volatile int i = 0;
public void foo() {
if (i == 0) i = i + 1;
}
変数が volatile であると宣言されているため、読み取りと書き込みがメイン メモリにフラッシュされることになりますが、上記のコードは本質的に安全ではありません。このようなメソッドの唯一の安全な実装は次のようなものになります。
int i = 0;
public synchronized void foo() {
if (i == 0) i = i + 1;
}
では、どちらを優先すべきでしょうか?さて、フィールドの値に応じてフィールドを変更する複数のスレッドがある場合(つまり、比較して設定)、その後 synchronized
が唯一の安全な解決策です。
次のように言うことも価値があります。 パフォーマンスのオーバーヘッド synchronized
問題はありません (圧倒的多数の場合)。同期パフォーマンスの問題は通常、不要なコードのボトルネック、デッドロック、またはライブロックが原因であり、必要に応じて軽減できます。どれでも 純粋なクロックサイクル オーバーヘッドは、アプリケーションが実行する他の処理に比べて小さく見えます。ファイル IO、データベース クエリ、リモート処理など。
揮発性は確かに行く方法です。 それが育ったので、私はあなたが知っているんだけど、:あなたは、より複雑な操作をしたいと思った場合(例えば値をインクリメント)あなたがsyncrhonizeする必要があります。私は++変数のいずれかのタイプのためのスレッドセーフされることはありません。あなたは同期する必要があります。 I ++、それが実際にあるので、のような1つの以上の動作ます。
はない:それはあなたがAtomicDoubleを使用することができることを表しますがありました現在、無AtomicDoubleでは、java.util.concurrent.atomic
最後に新しい値に設定する必要があなたがX上で複数の操作を行っている場合は、どのようなので、これまでのロックなしでスレッドセーフな方法でこれを行うことが可能である、そしてそれは、スレッドセーフである必要があり比較し、設定し使用して。例:
AtomicLong x = new AtomicLong(SomeValue);
public void doStuff() {
double oldX;
double newX;
do {
oldX = x.get();
newX = calculateNewX(oldX);
} while (!x.compareAndSet
(Double.doubleToLongBits(oldX), Double.doubleToLongBits(newX)));
xの値は、あなたがそれを読んで最後の時間以降に変更された場合のcompareAndSetが表示されますので、これは動作します。 xが変更されているなら、あなたは何度も繰り返し計算を行うことを強制し、それを設定し、再試行されます。
あなたはもちろん、代わりにこれらのdoubleToLongBits変換を行うの独自のAtomicDoubleを実装することができます。 AtomicFieldUpdaterを見てみましょう。
KDSRathore、あなたには、いくつかの明示的なロックを使用するか、またはいくつかのダミーObjectオブジェクト=新しいオブジェクトを(行うことができます)、あなたは二重の
のセッター/ゲッターに同期されていますOracleのドキュメントによると、 に、あなたはダブルオブジェクトを参照するために揮発使用することができます:
volatile Double x = y;
「を書き込むのと参照の読み込みに関係なく、それらは32ビットまたは64ビット値として実装されているかどうかを、常にアトミックである。」