Javaにおける揮発性と同期性の違い 質問する

Javaにおける揮発性と同期性の違い 質問する

Java で変数を として宣言するvolatileことと、常にブロック内で変数にアクセスすることの違いが知りたいです。synchronized(this)

この記事によるとhttp://www.javamex.com/tutorials/synchronization_volatile.shtml語るべきことはたくさんありますし、違いもたくさんありますが、類似点もいくつかあります。

私は特にこの情報に興味があります:

...

  • 揮発性変数へのアクセスはブロックされる可能性がありません。単純な読み取りまたは書き込みのみを実行するため、同期ブロックとは異なり、ロックを保持することはありません。
  • 揮発性変数へのアクセスではロックが保持されないため、読み取り、更新、書き込みをアトミック操作として実行したい場合には適していません (「更新を見逃す」覚悟がない限り)。

読み取り、更新、書き込みとはどういう意味ですか? 書き込みも更新ではないのですか、それとも単に更新が読み取りに依存する書き込みであることを意味しているのですか?

何よりも、ブロックvolatileを介して変数にアクセスするよりも、変数を宣言する方が適切なのはいつでしょうかsynchronized? 入力に依存する変数に使用するのは良い考えでしょうか? たとえば、レンダリング ループを介して読み取られ、キー押下イベントによって設定される というvolatile変数があるとします。render

ベストアンサー1

スレッドの安全性には2 つの側面があることを理解することが重要です。

  1. 実行制御、および
  2. メモリの可視性

1 つ目は、コードがいつ実行されるか (命令が実行される順序を含む) と、コードが同時に実行できるかどうかを制御することに関係し、2 つ目は、実行された内容のメモリ内の効果が他のスレッドにいつ表示されるかに関係します。各 CPU には、メイン メモリとの間に複数のレベルのキャッシュがあるため、異なる CPU またはコアで実行されているスレッドは、メイン メモリのプライベート コピーを取得して操作できるため、特定の瞬間に「メモリ」を異なる方法で表示できます。

を使用すると、synchronized他のスレッドが同じオブジェクトのモニター (またはロック) を取得できなくなるため、同じオブジェクトで同期によって保護されているすべてのコード ブロックが同時に実行されなくなります。同期によって、「事前発生」メモリ バリア作成され、メモリの可視性制約が発生します。これにより、あるスレッドがロックを解放するまでに行われたすべての処理は、その後同じロックを取得する別のスレッドには、ロックを取得する前に行われたように見えます。実際的には、現在のハードウェアでは、これにより通常、モニターが取得されると CPU キャッシュがフラッシュされ、モニターが解放されるとメイン メモリに書き込まれます。どちらも (比較的) コストがかかります。

volatile一方、 を使用すると、 volatile 変数へのすべてのアクセス (読み取りまたは書き込み) がメイン メモリに対して行われるようvolatileに強制され、実質的に volatile 変数が CPU キャッシュから排除されます。これは、変数の可視性が正しいことだけが要求され、アクセスの順序は重要ではないアクションで役立ちます。 を使用すると、 と の扱いも変更されlongdoubleそれらへのアクセスがアトミックであることが要求されます。一部の (古い) ハードウェアではロックが必要になる場合がありますが、最新の 64 ビット ハードウェアでは必要ありません。Java 5+ の新しい (JSR-133) メモリ モデルでは、 volatile のセマンティクスが強化され、メモリの可視性と命令の順序付けに関して synchronized とほぼ同等になりました ( を参照)。http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#揮発性)。可視性のために、揮発性フィールドへの各アクセスは、半分の同期のように動作します。

新しいメモリ モデルでは、依然として volatile 変数を相互に並べ替えることはできません。違いは、それらの変数の周りの通常のフィールド アクセスを並べ替えることがそれほど簡単ではないことです。 volatile フィールドへの書き込みは、モニターの解放と同じメモリ効果があり、 volatile フィールドからの読み取りは、モニターの取得と同じメモリ効果があります。 実際、新しいメモリ モデルでは、 volatile フィールド アクセスと他のフィールド アクセス (volatile かどうかに関係なく) の並べ替えに厳しい制約が課されるため、Avolatile フィールドに書き込むときにスレッドから見えていたものはすべて、読み取り時にfスレッドから見えるようになります。Bf

--JSR 133 (Java メモリ モデル) FAQ

したがって、現在では、両方の形式のメモリ バリア (現在の JMM 下) によって命令の並べ替えバリアが発生し、コンパイラまたはランタイムがバリアを越えて命令を並べ替えることができなくなります。古い JMM では、 volatile によって並べ替えが妨げられることはありませんでした。これは重要なことです。メモリ バリアとは別に、課せられる唯一の制限は、特定のスレッドの場合、コードの実際の効果は、命令がソースに出現する順序どおりに実行された場合と同じになるということであるためです。

揮発性の使用法の 1 つは、共有されているが不変のオブジェクトをオンザフライで再作成し、他の多くのスレッドが実行サイクルの特定の時点でオブジェクトへの参照を取得する場合です。再作成されたオブジェクトが公開されたら、他のスレッドがそのオブジェクトを使い始める必要がありますが、完全な同期やそれに伴う競合やキャッシュのフラッシュなどの追加のオーバーヘッドは必要ありません。

// Declaration
public class SharedLocation {
    static public volatile SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

具体的には、読み取り、更新、書き込みに関する質問についてです。次の安全でないコードを考えてみましょう。

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

ここで、updateCounter() メソッドが同期されていないため、2 つのスレッドが同時にこのメソッドに入る可能性があります。発生する可能性のあるさまざまな組み合わせのうちの 1 つは、スレッド 1 が counter==1000 のテストを実行し、true であることがわかり、一時停止されるというものです。次に、スレッド 2 が同じテストを実行し、true であることがわかり、一時停止されます。次に、スレッド 1 が再開し、counter を 0 に設定します。次に、スレッド 2 が再開し、スレッド 1 からの更新を逃したため、再び counter を 0 に設定します。これは、私が説明したようにスレッドの切り替えが発生しなくても発生する可能性がありますが、これは単に、counter の 2 つの異なるキャッシュされたコピーが 2 つの異なる CPU コアに存在し、スレッドがそれぞれ別のコアで実行されているためです。さらに言えば、キャッシュのためだけに、1 つのスレッドの counter が 1 つの値になり、もう 1 つのスレッドの counter がまったく異なる値になる可能性もあります。

この例で重要なのは、変数カウンタがメイン メモリからキャッシュに読み込まれ、キャッシュで更新され、メモリ バリアが発生したとき、またはキャッシュ メモリが他の目的で必要になったときに、後で不確定な時点でのみメイン メモリに書き戻されることです。カウンタを作成するだけでは、volatileこのコードのスレッド セーフには不十分です。最大値のテストと割り当ては、read+increment+write次のような非アトミック マシン命令のセットである増分を含む個別の操作であるためです。

MOV EAX,counter
INC EAX
MOV counter,EAX

揮発性変数は、それらに対して実行されるすべての操作が「アトミック」である場合にのみ役立ちます。たとえば、完全に形成されたオブジェクトへの参照が読み取りまたは書き込みのみである私の例 (そして、実際には、通常は単一のポイントからのみ書き込まれます) などです。別の例としては、コピーオンライト リストをサポートする揮発性配列参照があります。この場合、配列は、最初に参照のローカル コピーを取得することによってのみ読み取られます。

おすすめ記事