アトミック/揮発性/同期はどのように内部的に機能しますか?
次のコードブロックの違いは何ですか?
コード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つのスレッドが同時に同期ブロックに入ることはできないと思います...私は正しいですか?これが真実なら、synchronized
なしでatomic.incrementAndGet()
はどのように機能するのでしょうか。そしてそれはスレッドセーフですか?
そして、内部変数と揮発性変数/原子変数への書き込みの違いは何ですか?私はスレッドが変数のローカルコピーを持っていることをある記事で読みました - それは何ですか?
あなたは彼らが内部的にどのように働くのかを具体的に質問しています。
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
それは基本的にメモリから値を読み込み、それをインクリメントしてメモリに戻す。これはシングルスレッドで動作しますが、今日ではマルチコア、マルチCPU、マルチレベルのキャッシュの時代には正しく動作しません。まず最初に競合状態(複数のスレッドが同時に値を読み取ることができます)が導入されますが、可視性の問題もあります。値は "local" CPUメモリ(キャッシュ)にのみ格納され、他のCPU /コア(つまりスレッド)からは見えないかもしれません。これが、スレッド内の変数のローカルコピーを参照する人が多い理由です。非常に危険です。この普及しているが壊れたスレッド停止コードを考えてみます。
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
volatile
をstopped
変数に追加すれば問題なく動作します - 他のスレッドがpleaseStop()
メソッドを介してstopped
変数を変更した場合、その変更は作業スレッドの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
)を使用した場合でも、コードは正しくありません。 2つのスレッドが最初にi
からtemp
を同期的に(temp
内でローカルに同じ値を持つ)読み取り、次に最初のスレッドが新しい値をi
に割り当て(1から6まで)、他のスレッドが同じことを行います(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;
}
}
変数をvolatileとして宣言すると、その値を変更すると、その変数の実際のメモリ記憶域がすぐに影響を受けます。コンパイラは、変数に対して行われた参照を最適化することはできません。これにより、あるスレッドが変数を変更したときに、他のすべてのスレッドがすぐに新しい値を参照するようになります。 (これは不揮発性変数に対して保証されていません。)
atomic変数を宣言すると、その変数に対して行われた操作は必ずアトミックに行われます。つまり、操作のすべてのサブステップが実行されるスレッド内で完了し、他のスレッドによって中断されることはありません。たとえば、インクリメントテストでは、変数をインクリメントしてから別の値と比較する必要があります。アトミック操作は、これら2つのステップがあたかも単一の不可分/中断不可能な操作であるかのように完了することを保証します。
同期化変数へのすべてのアクセスは、一度に1つのスレッドだけがその変数にアクセスすることを許可し、他のすべてのスレッドはそのアクセススレッドがその変数へのアクセスを解放するのを待ちます。
同期アクセスはアトミックアクセスに似ていますが、アトミック操作は一般的に低レベルのプログラミングで実装されます。また、ある変数へのいくつかのアクセスのみを同期させ、他のアクセスを非同期にすることを可能にする(例えば、ある変数へのすべての書き込みを同期させるが、その変数からの読み取りは一切同期させない)。
アトミック性、同期性、およびボラティリティは独立した属性ですが、通常は変数にアクセスするための適切なスレッド協調を強化するために組み合わせて使用されます。
補遺(2016年4月)
変数への同期アクセスは通常、モニタまたはセマフォを使用して実装されます。これらは低レベルミューテックス(相互排除)メカニズムで、スレッドは変数またはコードブロックの制御を独占的に取得し、他のすべてのスレッドに強制的に制御させることができます。彼らも同じミューテックスを取得しようとするかどうか待ちます。所有スレッドがミューテックスを解放すると、別のスレッドが順番にミューテックスを取得できます。
補遺(2016年7月)
同期はオブジェクトで発生します。つまり、クラスの同期メソッドを呼び出すと、呼び出しのthis
オブジェクトがロックされます。静的同期メソッドはClass
オブジェクト自体をロックします。
同様に、synchronizedブロックに入るには、メソッドのthis
オブジェクトをロックする必要があります。
つまり、同期化されたメソッド(またはブロック)は、異なるオブジェクトをロックしている場合、同時に複数のスレッドで実行できますが、スレッドは1つだけです。与えられた単一のオブジェクトに対して同期メソッド(またはブロック)を一度に実行できます。
揮発性:
volatile
はキーワードです。 volatile
は、すべてのスレッドがキャッシュの代わりにメインメモリから変数の最新の値を取得するように強制します。揮発性変数にアクセスするためにロックは必要ありません。すべてのスレッドが同時に揮発性変数値にアクセスできます。
volatile
変数を使用すると、揮発性変数への書き込みは、その同じ変数の後続の読み取りとの間にビフォアビフォア関係を確立するため、メモリの一貫性エラーのリスクが軽減されます。
これは、volatile
変数への変更が常に他のスレッドから見えることを意味します。さらに、スレッドがvolatile
変数を読み込むとき、は、volatileへの最新の変更だけでなく、コードの副作用も認識します。それは変化を導いた。
使用する場合:1つのスレッドがデータを変更し、他のスレッドが最新のデータ値を読み取る必要があります。他のスレッドは何らかのアクションを取りますが、データを更新しません。
AtomicXXX:
AtomicXXX
クラスは、単一変数に対するロックフリーのスレッドセーフなプログラミングをサポートします。これらのAtomicXXX
クラス(AtomicInteger
など)は、複数のスレッドでアクセスされている揮発性変数の変更によるメモリの不整合エラー/副作用を解決します。
使用する場合:複数のスレッドがデータを読み書きすることができます。
同期:
synchronized
は、メソッドまたはコードブロックを保護するためのキーワードです。 methodをsynchronizedにすることで、2つの効果があります。
まず、同じオブジェクトに対してsynchronized
メソッドを2回呼び出してインターリーブすることはできません。あるスレッドが1つのオブジェクトに対してsynchronized
メソッドを実行しているとき、同じオブジェクトに対してsynchronized
メソッドを呼び出す(実行を中断する)他のすべてのスレッドは、そのオブジェクトに対する最初のスレッドが完了するまでブロックします。
2つ目は、synchronized
メソッドが終了すると、それ以降の同じオブジェクトに対するsynchronized
メソッドの呼び出しとの間に、before-before関係が自動的に確立されます。これにより、オブジェクトの状態への変更がすべてのスレッドから見えるようになります。
使用する場合:複数のスレッドがデータを読み取って変更することができます。ビジネスロジックはデータを更新するだけでなく、アトミックオペレーションも実行します
実装が異なっていてもAtomicXXX
はvolatile + synchronized
と同等です。 AmtomicXXX
は、volatile
変数+ compareAndSet
メソッドを拡張しますが、同期は使用しません。
関連するSEの質問:
読むべきよい記事:(上記の内容はこれらのドキュメンテーションページから取られます)
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つのスレッドが同時にSynchronizeブロックに入ることはできないことを私は知っています
2つのスレッドが同じオブジェクトの同期ブロックに2回入ることはできません。これは、2つのスレッドが異なるオブジェクトで同じブロックに入ることができることを意味します。この混乱はこのようなコードにつながる可能性があります。
private Integer i = 0;
synchronized(i) {
i++;
}
毎回異なるオブジェクトをロックする可能性があるため、これは予想どおりには動作しません。
これがtrueよりも、このatomic.incrementAndGet()がSynchronizeなしでどのように機能するか?そしてスレッドセーフですか?
はい。スレッドセーフを実現するためにロックを使用しません。
それらがどのように詳細に機能するのか知りたい場合は、それらのコードを読むことができます。
そして、内部の読み書きとVolatile Variable/Atomic Variableの違いは何ですか?
アトミッククラスはvolatile fields。を使います。フィールドに違いはありません。違いは実行される操作です。 AtomicクラスはCompareAndSwapまたはCAS操作を使用します。
私はスレッドがそれが何であるかの変数のローカルコピーを持っていることをいくつかの記事で読みました?
私はそれが各CPUがそれ自身のキャッシュされたメモリの見解を持っているという事実に言及していると仮定することができるだけです。 CPUが一貫したデータビューを持つようにするには、スレッドセーフテクニックを使用する必要があります。
これは、メモリが共有されているときに少なくとも1つのスレッドがそれを更新する場合にのみ問題になります。
揮発性+同期は、CPUへの複数の命令を含む操作(ステートメント)を完全にアトミックにするための確実なソリューションです。
たとえば、次のようにします。volatile int i = 2; i ++、これはi = i + 1に他なりません。この文の実行後、iはメモリ内の値3になります。これには、i(2)のメモリから既存の値を読み込み、CPUアキュムレータレジスタにロードし、既存の値を1つインクリメントして(2 + 1 = 3アキュムレータで)計算を行い、その増分値を書き戻すメモリに戻ります。 iの値は揮発性ですが、これらの操作はアトミックではありません。揮発性であることは、メモリからの単一の読み書きがアトミックであり、MULTIPLEではないことだけを保証します。したがって、私たちはそれを愚かな証拠のアトミックステートメントにするために、i ++の周りでも同期させる必要があります。 1つのステートメントに複数のステートメントが含まれているという事実を忘れないでください。
説明が十分明確であることを願っています。
Java volatile修飾子は、通信がスレッド間で行われることを保証するための特別なメカニズムの一例です。あるスレッドが揮発性変数に書き込みを行い、別のスレッドがその書き込みを見た場合、最初のスレッドはその揮発性変数への書き込みを実行するまで、2番目のスレッドにメモリの内容のすべてについて伝えます。
アトミック操作他の操作からの干渉なしに、タスクの単一の単位で実行されます。データの矛盾を避けるために、アトミック操作はマルチスレッド環境で必要です。
同期Vs原子Vs揮発性:
1。 VolatileとAtomicは変数にのみ適用されますが、Synchronizedはmethodに適用されます。
2。揮発性はオブジェクトの原子性/一貫性ではなく可視性を保証しますが、他の両方は可視性と原子性を保証します。
。 RAMに揮発性変数が格納されているためアクセスは高速ですが、同期キーワードを使用せずにスレッドセーフまたは同期を実現することはできません。
4。同期ブロックまたは同期メソッドとして同期実装されていますが、どちらも同期していません。 synchronizedキーワードを使用して安全に複数行のコードをスレッド化することはできますが、どちらを使用しても同じことは達成できません。
5。同期は同じクラスオブジェクトまたは異なるクラスオブジェクトをロックできますが、どちらもロックできません。
見逃したことがあれば訂正してください。