あなたは具体的にそれらが内部でどのように機能するかについて尋ねているので、ここにいます:
同期しない
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つのスレッドは最初iにtemp同期的に(ローカルで同じ値を持つ)を読み取ってから、最初のスレッドが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;
}
}