286

volatile変数を宣言することと、常にsynchronized(this)Javaのブロック内の変数にアクセスすることの違いについて疑問に思っていますか?

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

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

...

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

read-update-writeとはどういう意味ですか? 書き込みも更新ではありませんか、それとも単に更新が読み取りに依存する書き込みであることを意味しますか?

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

4

4 に答える 4

440

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

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

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

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

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

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

-- 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.

具体的には、読み取り、更新、書き込みの質問について話します。次の安全でないコードを検討してください。

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

現在、 updateCounter() メソッドが同期されていないため、2 つのスレッドが同時にこのメソッドに入る可能性があります。発生する可能性のある多くの順列の 1 つは、スレッド 1 が counter==1000 のテストを実行し、それが true であることを検出してから中断されることです。次に、スレッド 2 が同じテストを実行し、それが true であることを確認して中断されます。次に、スレッド 1 が再開し、カウンターを 0 に設定します。次に、スレッド 2 が再開し、スレッド 1 からの更新に失敗したため、カウンターを再び 0 に設定します。これは、説明したようにスレッドの切り替えが発生しない場合でも発生する可能性があります。これは、2 つの異なるキャッシュされたカウンターのコピーが 2 つの異なる CPU コアに存在し、スレッドがそれぞれ別のコアで実行されたためです。さらに言えば、1 つのスレッドが 1 つの値でカウンターを持ち、もう 1 つのスレッドがキャッシュのためにまったく異なる値でカウンターを持つ可能性があります。

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

MOV EAX,counter
INC EAX
MOV counter,EAX

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

于 2010-08-19T07:48:33.657 に答える
110

volatileフィールド修飾子であり、synchronizedはコード ブロックメソッドを変更します。したがって、これら 2 つのキーワードを使用して、単純なアクセサーの 3 つのバリエーションを指定できます。

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()i1現在のスレッドに現在格納されている値にアクセスします。スレッドは変数のローカル コピーを持つことができ、データは他のスレッドに保持されているデータと同じである必要はありません。特に、別のスレッドがそのスレッドで更新された可能性がi1ありますが、現在のスレッドの値はそれとは異なる可能性があります。値を更新しました。実際、Java には「メイン」メモリの概念があり、これは変数の現在の「正しい」値を保持するメモリです。スレッドは変数のデータの独自のコピーを持つことができ、スレッドのコピーは「メイン」メモリとは異なる場合があります。したがって、実際には、「メイン」メモリの値が1の場合i1、スレッド 1 の値が2の場合、i1およびスレッド2 の場合が可能です。スレッド 1 とスレッド2 の両方がi1を更新したが、それらの更新された値がまだ「メイン」メモリまたは他のスレッドに伝播されていない場合、値は3になります。i1

一方、「メイン」メモリからgeti2()の値に効果的にアクセスします。i2volatile 変数は、「メイン」メモリに現在保持されている値とは異なる変数のローカル コピーを持つことはできません。事実上、volatile として宣言された変数は、すべてのスレッド間でデータを同期する必要があります。これにより、任意のスレッドで変数にアクセスまたは更新するたびに、他のすべてのスレッドが同じ値をすぐに参照できるようになります。一般に、揮発性変数は、「プレーン」変数よりもアクセスと更新のオーバーヘッドが高くなります。通常、効率を高めるために、スレッドは独自のデータのコピーを持つことができます。

揮発性と同期には 2 つの違いがあります。

まず同期は、一度に 1 つのスレッドのみにコード ブロックを実行させることができるモニターのロックを取得および解放します。これは、同期のよく知られた側面です。ただし、同期はメモリも同期します。実際、同期はスレッド メモリ全体を「メイン」メモリと同期します。したがって、実行するgeti3()と次のようになります。

  1. スレッドは、オブジェクト this のモニターでロックを取得します。
  2. スレッド メモリはすべての変数をフラッシュします。つまり、すべての変数を「メイン」メモリから効果的に読み取ります。
  3. コード ブロックが実行されます (この場合、戻り値を i3 の現在の値に設定します。これは、「メイン」メモリからリセットされたばかりの可能性があります)。
  4. (変数への変更は通常、「メイン」メモリに書き出されますが、geti3() では変更はありません。)
  5. スレッドは、オブジェクト this のモニターのロックを解放します。

そのため、volatile はスレッド メモリと「メイン」メモリの間で 1 つの変数の値のみを同期しますが、synchronized はスレッド メモリと「メイン」メモリの間ですべての変数の値を同期し、モニターをロックおよび解放してブートします。明らかに同期は、揮発性よりも多くのオーバーヘッドを持つ可能性があります。

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

于 2010-08-19T07:50:55.973 に答える
24

synchronizedメソッドレベル/ブロックレベルのアクセス制限修飾子です。1 つのスレッドがクリティカル セクションのロックを所有していることを確認します。ロックを所有するスレッドのみがsynchronizedブロックに入ることができます。他のスレッドがこのクリティカル セクションにアクセスしようとしている場合、現在の所有者がロックを解除するまで待機する必要があります。

volatileすべてのスレッドがメインメモリから変数の最新の値を取得するように強制する変数アクセス修飾子です。volatile変数にアクセスするためにロックは必要ありません。すべてのスレッドが同時に volatile 変数値にアクセスできます。

volatile variable : Datevariable を使用する良い例です。

Date variable を作成したとしますvolatile。この変数にアクセスするすべてのスレッドは、すべてのスレッドが実際の (実際の) 日付値を表示するように、常にメイン メモリから最新のデータを取得します。同じ変数に対して異なる時間を示す異なるスレッドは必要ありません。すべてのスレッドは正しい日付値を表示する必要があります。

ここに画像の説明を入力

コンセプトの理解を深めるために、この記事をご覧ください。volatile

ローレンス・ドルはあなたのread-write-update query.

その他のお問い合わせについて

同期を介して変数にアクセスするよりも、変数を揮発性として宣言する方が適しているのはいつですか?

volatileDate 変数で説明した例のように、すべてのスレッドが変数の実際の値をリアルタイムで取得する必要があると思われる場合は、使用する必要があります。

入力に依存する変数に volatile を使用するのは良い考えですか?

答えは最初のクエリと同じです。

理解を深めるには、この記事を参照してください。

于 2015-12-18T12:43:08.787 に答える