回答:
主な用途は2つありますAtomicInteger
。
incrementAndGet()
多くのスレッドで同時に使用できるアトミックカウンター(など)として
非ブロッキングアルゴリズムを実装するための比較およびスワップ命令(compareAndSet()
)をサポートするプリミティブとして。
以下は、BrianGöetzのJava Concurrency In Practiceのノンブロッキング乱数ジェネレーターの例です。
public class AtomicPseudoRandom extends PseudoRandom {
private AtomicInteger seed;
AtomicPseudoRandom(int seed) {
this.seed = new AtomicInteger(seed);
}
public int nextInt(int n) {
while (true) {
int s = seed.get();
int nextSeed = calculateNext(s);
if (seed.compareAndSet(s, nextSeed)) {
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
...
}
ご覧のとおり、基本的にはとほぼ同じように機能しますが、インクリメントではなくincrementAndGet()
任意の計算(calculateNext()
)を実行します(そして、結果を処理してから戻ります)。
read
とwrite that value + 1
操作の間のカウンターを変更する場合、古い更新を上書きするのではなく、これを検出することを保証します(「失われた更新」問題を回避します)。これは実際には特殊なケースですcompareAndSet
-古い値がだった2
場合、クラスは実際に呼び出しますcompareAndSet(2, 3)
-そのため、別のスレッドがその間に値を変更した場合、インクリメントメソッドは事実上最初から再開します。
私が考えることができる絶対的に単純な例は、アトミック操作をインクリメントすることです。
標準の整数で:
private volatile int counter;
public int getNextUniqueIndex() {
return counter++; // Not atomic, multiple threads could get the same result
}
AtomicIntegerの場合:
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
後者は、すべてのアクセスの同期に頼る必要なく、単純な変更効果(特にカウント、または一意のインデックス付け)を実行する非常に簡単な方法です。
より複雑な同期フリーロジックを使用して使用することができるcompareAndSet()
楽観的ロックのタイプとして-これに基づいて、現在の値、計算結果を取得し、この結果を設定する場合に限っ値がまだ他に、計算を行う再起動するために用いられる入力である-しかし、カウントの例は非常に便利です。AtomicIntegers
複数のスレッドが関与しているというヒントがある場合は、カウントおよびVM全体の一意のジェネレーターを使用することがよくあります。これは、操作が非常に簡単であるため、プレーンを使用するのは時期尚早な最適化だとほとんど考えています。ints
。
ほとんどの場合ints
、適切なsynchronized
宣言を使用して同じ同期の保証を達成できますがAtomicInteger
、すべてのメソッドで発生する可能性のあるインターリーブやモニターの保持について心配する必要がなく、スレッドの安全性が実際のオブジェクト自体に組み込まれているのが優れています。たまたまint
値にアクセスします。正しいモニターのセットを事前に取得するためgetAndIncrement()
に戻っi++
て覚えている(または覚えていない)ときよりも、呼び出し時に誤ってスレッドセーフ性に違反することははるかに困難です。
AtomicIntegerのメソッドを見ると、intでの一般的な操作に対応する傾向があることがわかります。例えば:
static AtomicInteger i;
// Later, in a thread
int current = i.incrementAndGet();
これのスレッドセーフバージョンです:
static int i;
// Later, in a thread
int current = ++i;
方法は次のようにマッピングします
++i
さi.incrementAndGet()
i++
れi.getAndIncrement()
--i
ているi.decrementAndGet()
i--
さi.getAndDecrement()
i = x
れi.set(x)
x = i
ていますx = i.get()
compareAndSet
またはのような他の便利な方法もありますaddAndGet
の主な用途はAtomicInteger
、マルチスレッドコンテキストで、を使用せずに整数に対してスレッドセーフな操作を実行する必要がある場合ですsynchronized
。プリミティブ型の割り当てと取得int
はすでにアトミックですが、アトミックAtomicInteger
ではない多くの操作が付属していint
ます。
最も簡単なものはありますgetAndXXX
かxXXAndGet
。たとえばgetAndIncrement()
、i++
これはアトミックではありませんが、実際には3つの操作(取得、追加、割り当て)のショートカットであるため、アトミックではありません。compareAndSet
セマフォ、ロック、ラッチなどの実装に非常に役立ちます。
を使用するAtomicInteger
と、同期を使用して同じことを実行するよりも高速で読みやすくなります。
簡単なテスト:
public synchronized int incrementNotAtomic() {
return notAtomic++;
}
public void performTestNotAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
incrementNotAtomic();
}
System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}
public void performTestAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
atomic.getAndIncrement();
}
System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}
Java 1.6を搭載した私のPCでは、アトミックテストは3秒で実行され、同期テストは約5.5秒で実行されます。ここでの問題は、同期する操作(notAtomic++
)が本当に短いことです。したがって、同期のコストは操作と比較して非常に重要です。
原子性のほかに、AtomicIntegerはInteger
、たとえばMap
値としてsの可変バージョンとして使用できます。
AtomicInteger
デフォルトのequals()
実装を使用しているため、マップキーとして使用したくないと思います。これは、マップで使用した場合のセマンティクスが期待するものとはほぼ異なります。
たとえば、あるクラスのインスタンスを生成するライブラリがあります。これらのインスタンスはサーバーに送信されるコマンドを表し、各コマンドには一意のIDが必要であるため、これらの各インスタンスには一意の整数IDが必要です。複数のスレッドが同時にコマンドを送信できるため、AtomicIntegerを使用してそれらのIDを生成しています。別のアプローチは、ある種のロックと通常の整数を使用することですが、それはどちらも遅く、エレガントではありません。
Java 8では、アトミッククラスが2つの興味深い関数で拡張されています。
どちらもupdateFunctionを使用して、アトミック値の更新を実行しています。違いは、1つ目は古い値を返し、2つ目は新しい値を返すことです。updateFunctionを実装すると、標準の操作よりも複雑な「比較および設定」操作を実行できます。たとえば、アトミックカウンターがゼロ未満にならないことを確認できます。通常は同期が必要ですが、ここではコードがロックされていません。
public class Counter {
private final AtomicInteger number;
public Counter(int number) {
this.number = new AtomicInteger(number);
}
/** @return true if still can decrease */
public boolean dec() {
// updateAndGet(fn) executed atomically:
return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
}
}
コードはJava Atomic Exampleから取得されました。
複数のスレッドからアクセスまたは作成できるオブジェクトにIDを与える必要がある場合は、通常AtomicIntegerを使用します。通常、オブジェクトのコンストラクターでアクセスするクラスの静的属性として使用します。
アトミック整数またはlongに対してcompareAndSwap(CAS)を使用して、非ブロッキングロックを実装できます。「TL2」ソフトウェアトランザクショナルメモリ紙はこれを説明します。
トランザクション処理されたすべてのメモリロケーションに、特別なバージョンの書き込みロックを関連付けます。最も単純な形式では、バージョン付きの書き込みロックは、CAS操作を使用してロックを取得し、ストアを使用してロックを解放する単一ワードのスピンロックです。ロックが取得されたことを示すために必要なのは1ビットだけなので、残りのロックワードを使用してバージョン番号を保持します。
それが説明していることは、最初に原子整数を読むことです。これを、無視されたロックビットとバージョン番号に分割します。現在のバージョン番号でロックビットがクリアされた状態でロックビットセットと次のバージョン番号にCAS書き込みを試みます。成功するまでループし、自分がロックを所有するスレッドになります。ロックビットをクリアした状態で現在のバージョン番号を設定してロックを解除します。このペーパーでは、ロックのバージョン番号を使用して、スレッドが書き込み時に一貫した読み取りセットを持つように調整する方法について説明しています。
この記事では、プロセッサがハードウェアで比較およびスワップ操作をサポートしているため、非常に効率的であると説明しています。それはまた主張します:
アトミック変数を使用する非ブロッキングCASベースのカウンターは、低から中程度の競合でロックベースのカウンターよりも優れたパフォーマンスを発揮します
私はAtomicIntegerを使ってダイニング哲学者の問題を解決しました。
私のソリューションでは、AtomicIntegerインスタンスを使用してフォークを表現しました。哲学者ごとに2つ必要です。各哲学者は1〜5の整数として識別されます。哲学者がフォークを使用すると、AtomicIntegerは哲学者の値を1〜5に保持します。それ以外の場合、フォークは使用されないため、AtomicIntegerの値は-1になります。 。
次に、AtomicIntegerを使用すると、1つのアトミック操作で、フォークが空いている(値==-1)かどうかを確認し、空いている場合はフォークの所有者に設定できます。以下のコードを参照してください。
AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){
if (Hungry) {
//if fork is free (==-1) then grab it by denoting who took it
if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
//at least one fork was not succesfully grabbed, release both and try again later
fork0.compareAndSet(p, -1);
fork1.compareAndSet(p, -1);
try {
synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork
lock.wait();//try again later, goes back up the loop
}
} catch (InterruptedException e) {}
} else {
//sucessfully grabbed both forks
transition(fork_l_free_and_fork_r_free);
}
}
}
compareAndSetメソッドはブロックしないため、スループットが向上し、より多くの作業が行われます。ご存じかもしれませんが、Dining Philosophers問題は、プロセスが作業を継続するためにリソースを必要とするように、リソースへの制御されたアクセスが必要な場合、つまりフォークが必要な場合に使用されます。
compareAndSet()関数の簡単な例:
import java.util.concurrent.atomic.AtomicInteger;
public class GFG {
public static void main(String args[])
{
// Initially value as 0
AtomicInteger val = new AtomicInteger(0);
// Prints the updated value
System.out.println("Previous value: "
+ val);
// Checks if previous value was 0
// and then updates it
boolean res = val.compareAndSet(0, 6);
// Checks if the value was updated.
if (res)
System.out.println("The value was"
+ " updated and it is "
+ val);
else
System.out.println("The value was "
+ "not updated");
}
}
印刷されるのは:以前の値:0値は更新され、6です別の簡単な例:
import java.util.concurrent.atomic.AtomicInteger;
public class GFG {
public static void main(String args[])
{
// Initially value as 0
AtomicInteger val
= new AtomicInteger(0);
// Prints the updated value
System.out.println("Previous value: "
+ val);
// Checks if previous value was 0
// and then updates it
boolean res = val.compareAndSet(10, 6);
// Checks if the value was updated.
if (res)
System.out.println("The value was"
+ " updated and it is "
+ val);
else
System.out.println("The value was "
+ "not updated");
}
}
印刷されたもの:以前の値:0値は更新されませんでした