1. ホーム

[解決済み】Javaにおけるvolatileとsynchronizedの違いについて

2022-04-15 18:47:54

質問

という変数宣言の違いが気になります。 volatile で、常にその変数にアクセスし synchronized(this) のブロックは、Java?

この記事によると http://www.javamex.com/tutorials/synchronization_volatile.shtml があり、多くの相違点がある一方で、類似点もあります。

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

...

  • volatile変数へのアクセスはブロックされる可能性がありません。単純な読み取りまたは書き込みを行うだけなので、同期ブロックとは異なり、ロックを保持することはありません。
  • volatile変数へのアクセスはロックを保持しないので、以下のようなケースには適していません。 読み取り-更新-書き込み をアトミック操作として使用することができます(quot;miss an update"を覚悟している場合を除く)。

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

なによりも、どのような場合に変数を宣言するのがより適切なのかというと volatile を通してアクセスするのではなく synchronized ブロックを作成しますか?を使用するのは良いアイデアでしょうか? volatile を入力に依存する変数に使用できますか?例えば render レンダリングループで読み込まれ、キー押下イベントによって設定される?

どのように解決するのですか?

があることを理解することが重要です。 スレッドセーフの側面。

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

1つ目は、コードがいつ実行されるか(命令の実行順序を含む)、同時に実行できるかどうかを制御することに関係し、2つ目は、実行されたことのメモリ上の効果が他のスレッドからいつ見えるようになるかに関係するものです。 各CPUはメインメモリとの間に数レベルのキャッシュを持つため、異なるCPUまたはコア上で動作するスレッドは、メインメモリのプライベートコピーを取得して作業することが許可されているため、任意の時点で異なるメモリ"を見ることができます。

使用方法 synchronized は、他のスレッドがモニタ(またはロック)を取得するのを防ぎます。 同じオブジェクトに対して これにより、同期化で保護されたすべてのコードブロックは 同じオブジェクトに を同時実行しないようにする。 同期 また あるスレッドがロックを解放するまでの間に行われたことはすべて、メモリ可視性制約を引き起こします。 が現れる。 を後から取得した別のスレッドに 同じロック を、ロックを取得する前に発生させることができます。実際のところ、現在のハードウェアでは、モニターを取得するときに CPU キャッシュをフラッシュし、モニターを解放するときにメインメモリに書き込みますが、どちらも(比較的)高価です。

使用方法 volatile 一方、volatile変数へのすべてのアクセス(読み取りまたは書き込み)はメインメモリで行われ、volatile変数を効果的にCPUキャッシュに入れないようにします。これは、単に変数の可視性が正しく、アクセスの順序が重要でないような動作に便利です。使用方法 volatile の扱いも変わります。 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フィールドからの読み込みはモニタ・アクイジションと同じメモリ効果を持ちます。事実上、新しいメモリモデルでは、揮発性フィールドへのアクセスと他のフィールドへのアクセス(揮発性かどうかにかかわらず)の順序変更に厳しい制約を課しているため、スレッド A に書き込んだとき、揮発性フィールド f がスレッドから見えるようになります。 B を読み込むと f .

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

つまり、(現在のJMMでは)どちらの形式のメモリバリアも、コンパイラやランタイムがバリアを越えて命令を並べ替えることを防ぐ命令並べ替えバリアを引き起こすのですね。古いJMMでは、volatileは再順序付けを防げませんでした。これは重要なことです。なぜなら、メモリバリアを除けば、唯一の制限事項が課されるからです。 特定のスレッドに対して この場合、コードの正味の効果は、ソースに表示される順番通りに命令を実行した場合と同じになります。

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

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

// Publishing 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.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
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.

read-update-writeの質問についてですが、具体的には。 次のような安全でないコードを考えてみましょう。

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

さて、updateCounter()メソッドが非同期化されているので、2つのスレッドが同時に入る可能性があります。 何が起こるか分からないが、スレッド 1 が counter==1000 のテストを行い、それが真であると判断して中断されることがある。次にスレッド-2が同じテストを行い、それが真であることがわかり、中断される。その後、スレッド-1 が再開してカウンタを 0 にセットします。その後、スレッド-2 が再開して、スレッド-1 からの更新を逃したため、再びカウンタを 0 にセットします。 これは、私が説明したようにスレッド切り替えが発生しない場合でも、単に2つの異なるCPUコアに2つの異なるキャッシュされたカウンタのコピーが存在し、スレッドがそれぞれ別のコアで実行されたために発生する可能性があります。つまり、あるスレッドがある値でカウンターを持ち、もう一方のスレッドがキャッシュのために全く別の値でカウンターを持つこともあり得るのです。

この例で重要なのは、変数 カウンター は、メインメモリからキャッシュに読み込まれ、キャッシュ内で更新された後、メモリバリアが発生したとき、あるいはキャッシュメモリが他のことに必要になったときに、不確定な時点でメインメモリに書き戻されるだけです。カウンターを volatile というのも、最大値のテストと代入は個別の操作であり、インクリメントも含めて非原子の read+increment+write のような機械命令です。

MOV EAX,counter
INC EAX
MOV counter,EAX

揮発性の変数は、次のような場合にのみ有効です。 すべて 例えば、完全に形成されたオブジェクトへの参照は、読み取りまたは書き込みのみです(実際、通常は1つのポイントから書き込むだけです)。他の例としては、コピーオンライトリストをバックアップする揮発性配列の参照が挙げられます。