アトミック/揮発性/同期の違いは何ですか?


297

アトミック/揮発性/同期化は内部的にどのように機能しますか

次のコードブロックの違いは何ですか?

コード1

private int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

コード2

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

コード3

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

volatile次のように機能しますか?です

volatile int i = 0;
void incIBy5() {
    i += 5;
}

に相当

Integer i = 5;
void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

2つのスレッドが同時に同期ブロックに入ることができないと思います...私は正しいですか?これが本当なら、それatomic.incrementAndGet()なしでどのように機能しsynchronizedますか?そしてそれはスレッドセーフですか?

そして、揮発性変数/原子変数への内部読み取りと書き込みの違いは何ですか?いくつかの記事で、スレッドに変数のローカルコピーがあることを読みました。これは何ですか。


5
コンパイルさえできないコードで、それは多くの質問をします。多分あなたはJava Concurrency in Practiceのような良い本を読むべきです。
JBニゼット2012年

4
@JBNizet正解です!!! 私はその本を持っています、それは簡単に原子の概念を持っていません、そして私はそれのいくつかの概念を得ていません。呪いのそれは著者のではなく私の間違いです。
hardik 2012年

4
実装方法を気にする必要はありません(OSによって異なります)。あなたが理解しなければならないのはコントラクトです:値はアトミックにインクリメントされ、他のすべてのスレッドは新しい値を見ることが保証されています。
JBニゼット2012年

回答:


392

あなたは具体的にそれらが内部でどのように機能するかについて尋ねているので、ここにいます:

同期しない

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

基本的にメモリから値を読み取り、インクリメントしてメモリに戻します。これはシングルスレッドで機能しますが、今日では、マルチコア、マルチCPU、マルチレベルのキャッシュの時代では、正しく機能しません。まず、競合状態(複数のスレッドが同時に値を読み取る可能性があります)が発生しますが、可視性の問題もあります。値は「ローカル」のCPUメモリ(一部のキャッシュ)にのみ格納され、他のCPU /コア(したがってスレッド)からは見えない可能性があります。多くの人がスレッド内の変数のローカルコピーを参照するのはこのためです。それは非常に危険です。この人気のある壊れたスレッド停止コードを考えてみましょう:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

public void pleaseStop() {
    stopped = true;
}

変数に追加volatileするstoppedと、正常に機能します。他のスレッドがメソッドstoppedを介して変数を変更した場合pleaseStop()、作業スレッドのwhile(!stopped)ループでその変更がすぐに表示されることが保証されます。:ところでこれは、いずれかのスレッドを中断見るための良い方法ではありません任意の使用せずに永遠に実行されているスレッドを停止する方法、特定のJavaスレッドを停止します

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

このAtomicIntegerクラスはCAS(compare-and-swap)低レベルCPU操作を使用します(同期は必要ありません!)現在の値が他の値と等しい(そして正常に返される)場合にのみ、特定の変数を変更できます。したがって、実行するgetAndIncrement()と実際にはループで実行されます(単純化された実際の実装)。

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

だから基本的に:読んでください。インクリメントされた値を保存しようとします。成功しなかった場合(値がと等しくない場合current)、読み取り、再試行してください。compareAndSet()ネイティブコード(アセンブリ)に実装されています。

volatile 同期なし

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

このコードは正しくありません。それは可視性の問題を修正します(volatile他のスレッドがに加えられた変更を確認できるようにしますcounter)が、依然として競合状態にあります。これは何度も説明されています。事前/事後の増分はアトミックではありません。

の唯一の副作用は、他のすべての関係者が最新バージョンのデータを参照できるように、キャッシュをvolatileフラッシュ」することです。これはほとんどの状況で厳しすぎる。これがvolatileデフォルトではない理由です。

volatile 同期なし(2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

上記と同じ問題ですiが、ではないため、さらに悪い問題privateです。競合状態はまだ存在しています。なぜそれが問題なのですか?たとえば、2つのスレッドがこのコードを同時に実行すると、出力は+ 5またはになり+ 10ます。ただし、変更が確実に表示されます。

複数の独立した synchronized

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

驚いたことに、このコードも正しくありません。実際、それは完全に間違っています。最初に、i変更しようとしているで同期しています(さらに、iプリミティブなので、Integerオートボクシングで作成された一時ファイルで同期していると思います...)完全に欠陥があります。次のように書くこともできます:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

2つのスレッドが同じロックで同じsynchronizedブロックに入ることはできません。この場合(および同様にコード内)、ロックオブジェクトは実行のたびに変化するため、効果はありません。synchronized

this同期にfinal変数(または)を使用した場合でも、コードは正しくありません。2つのスレッドは最初itemp同期的に(ローカルで同じ値を持つ)を読み取ってから、最初のスレッドがtemp新しい値i(たとえば1から6)を割り当て、もう1 つのスレッドが同じことを行います(1から6)。

同期は、読み取りから値の割り当てまでにわたる必要があります。最初の同期は効果がなく(読み取りintはアトミックです)、2番目の同期も同様です。私の意見では、これらは正しい形式です:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}

10
追加するのは、JVMが変数値をレジスターにコピーして操作することです。つまり、単一のCPU /コアで実行されているスレッドは、不揮発性変数の異なる値を引き続き参照できます。
David Harkness

@thomasz:compareAndSet(current、current + 1)は同期されていますか?2つのスレッドがこのメソッドを同時に実行しているときに何が起こるか?
hardik

@Hardik:compareAndSetCAS操作のラッパーです。私は私の答えのいくつかの詳細に行きます。
Tomasz Nurkiewicz 2012

1
@thomsasz:わかりました、私はこのリンクの質問を通り抜け、ジョンスキートが答えました、「他のスレッドが書き込みを行ったかどうかをチェックしないと、スレッドは揮発性変数を読み取ることができません。」しかし、1つのスレッドが書き込み操作の間にあり、2番目のスレッドがそれを読み込んでいる場合はどうなりますか?私が間違っている ??アトミック操作の競合状態ではないですか?
hardik

3
@Hardik:質問の回答を得るために別の質問を作成してください。ここでは、あなたと私だけです。コメントは質問に適していません。フォローアップできるように、ここに新しい質問へのリンクを投稿することを忘れないでください。
Tomasz Nurkiewicz

61

変数を揮発性として宣言すると、その値を変更すると、変数の実際のメモリストレージにすぐに影響します。コンパイラーは、変数への参照を最適化することはできません。これにより、1つのスレッドが変数を変更したときに、他のすべてのスレッドがすぐに新しい値を参照することが保証されます。(これは不揮発性変数では保証されません。)

アトミック変数を宣言すると、変数に対して行われた操作がアトミックに行われることが保証されます。つまり、操作のすべてのサブステップは、実行されるスレッド内で完了し、他のスレッドによって中断されません。たとえば、インクリメントとテストの操作では、変数をインクリメントしてから別の値と比較する必要があります。アトミック操作は、これらのステップの両方が、単一の不可分/割り込み不可能な操作であるかのように完了することを保証します。

変数へのすべてのアクセスを同期すると、一度に1つのスレッドのみが変数にアクセスでき、他のすべてのスレッドは、そのアクセススレッドが変数へのアクセスを解放するまで待機します。

同期アクセスはアトミックアクセスに似ていますが、アトミック操作は通常、プログラミングの低レベルで実装されます。また、変数への一部のアクセスのみを同期し、他のアクセスを非同期にすることも可能です(たとえば、変数へのすべての書き込みを同期しますが、変数からの読み取りは同期しません)。

原子性、同期、ボラティリティは独立した属性ですが、通常、変数にアクセスするための適切なスレッド連携を強制するために組み合わせて使用​​されます。

補遺 (2016年4月)

変数への同期アクセスは、通常、モニターまたはセマフォを使用して実装されます。これらは、スレッドが変数またはコードのブロックの制御を排他的に取得できるようにする低レベルのmutex(相互排他)メカニズムであり、他のすべてのスレッドも同じmutexを取得しようとすると強制的に待機させます。所有するスレッドがミューテックスを解放すると、別のスレッドがミューテックスを順番に取得できます。

補遺 (2016年7月)

同期はオブジェクトで発生します。つまり、クラスの同期されたメソッドをthis呼び出すと、呼び出しのオブジェクトがロックされます。静的同期メソッドはClassオブジェクト自体をロックします。

同様に、同期ブロックに入るにthisは、メソッドのオブジェクトをロックする必要があります。

つまり、同期されたメソッド(またはブロック)は、異なるオブジェクトをロックしている場合、複数のスレッドで同時に実行できますが、同期されたメソッド(またはブロック)を実行できるのは、特定の単一のオブジェクトに対して一度に1つのスレッドだけです。


25

揮発性:

volatileキーワードです。volatileすべてのスレッドが、キャッシュではなくメインメモリから変数の最新の値を取得するように強制します。揮発性変数にアクセスするためにロックは必要ありません。すべてのスレッドは、揮発性変数の値に同時にアクセスできます。

volatile変数を使用すると、揮発性変数への書き込みにより、同じ変数の後続の読み取りと前に発生する関係が確立されるため、メモリ整合性エラーのリスクが軽減されます。

これは、volatile変数への変更は常に他のスレッドから見えることを意味します。さらに、スレッドがvolatile変数を読み取るときに、揮発性に対する最新の変更だけでなく、変更を引き起こしたコードの副作用も確認することも意味します

使用するタイミング:1つのスレッドがデータを変更し、他のスレッドがデータの最新の値を読み取る必要があります。他のスレッドはいくつかのアクションを実行しますが、データを更新しません

AtomicXXX:

AtomicXXXクラスは、単一の変数でロックフリーのスレッドセーフなプログラミングをサポートします。これらのAtomicXXXクラス(などAtomicInteger)は、複数のスレッドでアクセスされた揮発性変数の変更によるメモリの不整合エラー/副作用を解決します。

使用する場合:複数のスレッドがデータを読み取り、変更できます。

同期:

synchronizedメソッドまたはコードブロックを保護するために使用されるキーワードです。メソッドを同期化すると、2つの効果があります。

  1. まず、synchronized同じオブジェクトで2つのメソッド呼び出しをインターリーブすることはできません。1つのスレッドがsynchronizedオブジェクトのメソッドを実行しているときsynchronized、同じオブジェクトのメソッドを呼び出す他のすべてのスレッドは、最初のスレッドがオブジェクトで完了するまでブロックします(実行を中断します)。

  2. 第2に、synchronizedメソッドが終了synchronizedすると、同じオブジェクトのメソッドの後続の呼び出しと、前に発生する関係が自動的に確立されます。これにより、オブジェクトの状態の変更がすべてのスレッドから見えるようになります。

使用する場合:複数のスレッドがデータを読み取り、変更できます。ビジネスロジックはデータを更新するだけでなく、アトミック操作も実行します

AtomicXXXvolatile + synchronized実装が異なる場合でも同等です。変数+ メソッドをAmtomicXXX拡張しvolatileますcompareAndSetが、同期は使用しません。

関連するSEの質問:

Javaでの揮発性と同期の違い

揮発性ブールvs AtomicBoolean

読むのに適した記事:(上記の内容は、これらのドキュメントページから取得されます)

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html


2
これは、実際にコード実行にどのように影響するかを理解する上で重要な、説明されているキーワード/機能の「発生前のセマンティクス」について実際に言及する最初の回答です。投票数の多い回答はこの側面を欠いています。
jhyot

5

2つのスレッドがSynchronizeブロックに同時に入ることができないことを知っています

2つのスレッドが同じオブジェクトの同期ブロックに2回入ることはできません。つまり、2つのスレッドが異なるオブジェクトの同じブロックに入ることができます。この混乱は、このようなコードにつながる可能性があります。

private Integer i = 0;

synchronized(i) {
   i++;
}

毎回異なるオブジェクトをロックする可能性があるため、これは期待どおりに動作しません。

これがtrueの場合、このatomic.incrementAndGet()はSynchronizeなしでどのように機能しますか?スレッドセーフですか?

はい。スレッドセーフを実現するためにロックを使用しません。

それらがどのように機能するかをもっと詳しく知りたい場合は、それらのコードを読むことができます。

そして、揮発性変数/原子変数への内部読み取りと書き込みの違いは何ですか?

Atomicクラスは揮発性フィールドを使用します。フィールドに違いはありません。違いは、実行される操作です。AtomicクラスはCompareAndSwapまたはCAS操作を使用します。

私はいくつかの記事でスレッドが変数のローカルコピーを持っていることを読みましたそれは何ですか?

私はそれが各CPUが他のすべてのCPUとは異なる可能性のあるメモリのキャッシュされた独自のビューを持っているという事実に言及しているとしか仮定できません。CPUでデータの一貫したビューを確保するには、スレッドセーフ技術を使用する必要があります。

これは、メモリが共有されている場合にのみ問題となり、少なくとも1つのスレッドがメモリを更新します。


@Aniket Thakur、本当によろしいですか?整数は不変です。そのため、i ++はおそらくint値を自動アンボックス化し、それをインクリメントしてから、以前と同じインスタンスではない新しいIntegerを作成します。iをfinalにすると、i ++を呼び出すときにコンパイラエラーが発生します。
fuemf5 2014

2

同期対原子対揮発性:

  • 揮発性とアトミックは変数にのみ適用され、同期化はメソッドに適用されます。
  • 揮発性はオブジェクトの原子性/一貫性ではなく可視性を保証しますが、その他は可視性と原子性を保証します。
  • 揮発性変数はRAMに格納されており、アクセスは高速ですが、スレッドセーフまたは同期の同期されたキーワードを達成できません。
  • 同期は、同期ブロックまたは同期メソッドとして実装されますが、両方は実装されません。同期キーワードを使用して、安全な複数行のコードをスレッド化できますが、両方を使用しても同じことはできません。
  • Synchronizedは、同じクラスオブジェクトまたは異なるクラスオブジェクトをロックできますが、両方はロックできません。

見落としがあったら訂正してください。


1

volatile +同期は、CPUへの複数の命令を含む操作(ステートメント)を完全にアトミックにするための間違いのないソリューションです。

たとえば、次のように言います。volatileint i = 2; i ++、これはi = i + 1にすぎません。これにより、このステートメントの実行後、メモリ内のiが値3になります。これには、i(2)のメモリから既存の値を読み取り、CPUアキュムレータレジスタにロードし、既存の値を1(アキュムレータでは2 + 1 = 3)でインクリメントしてから、そのインクリメントされた値を書き戻すことを含みます。記憶に戻る。これらの操作はアトミックではありませんが、iの値は揮発性です。iが揮発性であることは、メモリからのSINGLE読み取り/書き込みがアトミックであり、MULTIPLEではないことのみを保証します。したがって、私はそれを間違いのないアトミックステートメントに保つために、i ++の周りでも同期する必要があります。ステートメントには複数のステートメントが含まれていることを覚えておいてください。

説明が十分に明確であることを願っています。


1

Javaのvolatile修飾子は、スレッド間で通信が行われることを保証する特別なメカニズムの例です。1つのスレッドが揮発性変数に書き込み、別のスレッドがその書き込みを確認すると、最初のスレッドは、その揮発性変数への書き込みを実行するまで、メモリのすべての内容について2番目のスレッドに通知します。

アトミック操作は、他の操作からの干渉を受けることなく、単一のタスク単位で実行されます。アトミック操作は、データの不整合を回避するためにマルチスレッド環境で必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.