Javaには、Atomicで始まるクラスがいくつかあります。この一連のクラスをアトミック操作クラスと呼びます。アトミッククラスはCASと揮発性に基づいて構築されています。CASは非ブロッキングアルゴリズムの一般的な実装であり、ブロッキングアルゴリズムである同期よりも優れたパフォーマンスを発揮します。
最も単純なクラスであるAtomicIntegerを例にとってみましょう。これはint変数と同等であり、Intのi ++を実行する場合、アトミック操作ではありません。AtomicIntegerのincrementAndGetを使用すると、アトミック操作が保証されます。具体的なクラスは次のとおりです。
さらに面倒なことはせずに、例を挙げて話しましょう。
質問:2つのスレッドがあり、それぞれがグローバル整数変数iを1ずつインクリメントします。各スレッドは5000回実行されます。従来のintの使用法によると、コードは次のとおりです。
private static int m = 0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(2);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5000; j++) {
m++;
}
cdl.countDown();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5000; j++) {
m++;
}
cdl.countDown();
}
});
t1.start();
t2.start();
cdl.await();
System.out.println("result=" + m);
}
最後に、上記のコードを実行すると、結果は10000になる可能性がありますが、ほとんどの場合、10000ではなく、いくつかの乱数です。ここでの問題はm++です。m++に同期されたキーワードを追加すると、並行性の問題も解決できます。しかし、同期は重すぎます。したがって、アトミック操作クラスAtomicIntegerを使用して達成することを検討できます。具体的な実装コードは次のとおりです。
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(2);
AtomicInteger i = new AtomicInteger(0);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5000; j++) {
i.incrementAndGet();
}
cdl.countDown();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5000; j++) {
i.incrementAndGet();
}
cdl.countDown();
}
});
t1.start();
t2.start();
cdl.await();
System.out.println("result=" + i.get());
}
これで、何度実行しても、結果は常に10000になります。
説明:
- m ++は不可分操作ではありませんが、incrementAndGetは不可分操作メソッドです