JavaにおけるAtomicInteger
今日はJavaのAtomicIntegerを見ていきます。アトミック操作は他の操作の干渉なしに、単一のタスク単位で実行されます。アトミック操作は、データの不整合を防ぐために、マルチスレッド環境で必要です。
アトミックインテジャー
シンプルなマルチスレッドプログラムを作成しましょう。すべてのスレッドが共有カウント変数を4回増やします。つまり、2つのスレッドがある場合、処理が完了した後のカウント値は8になります。JavaAtomic.java
package com.scdev.concurrency;
public class JavaAtomic {
public static void main(String[] args) throws InterruptedException {
ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "t1");
t1.start();
Thread t2 = new Thread(pt, "t2");
t2.start();
t1.join();
t2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ProcessingThread implements Runnable {
private int count;
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count++;
}
}
public int getCount() {
return this.count;
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上記のプログラムを実行すると、カウント値が5、6、7、8の間で変化することに気付くでしょう。その理由は、count++はアトミックな操作ではないためです。そのため、1つのスレッドがその値を読み取り、1をインクリメントする間に、他のスレッドが古い値を読み取り、誤った結果につながります。この問題を解決するためには、count上の増分操作がアトミックであることを確認する必要があります。これは同期を使用して行うこともできますが、Java 5のjava.util.concurrent.atomicは、同期を使用せずにこのアトミック操作を実現するためのintとlongのラッパークラスを提供しています。
JavaのAtomicIntegerの例
常にカウント値が8となるアップデートされたプログラムがこちらです。なぜなら、AtomicIntegerメソッドのincrementAndGet()は現在の値を1でアトミックにインクリメントするからです。
package com.scdev.concurrency;
import java.util.concurrent.atomic.AtomicInteger;
public class JavaAtomic {
public static void main(String[] args) throws InterruptedException {
ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "t1");
t1.start();
Thread t2 = new Thread(pt, "t2");
t2.start();
t1.join();
t2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ProcessingThread implements Runnable {
private AtomicInteger count = new AtomicInteger();
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count.incrementAndGet();
}
}
public int getCount() {
return this.count.get();
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Concurrencyクラスを使用する利点は、同期化の心配が不要であることです。これにより、コードの読みやすさが向上し、エラーの可能性も減少します。また、リソースをロックする同期化よりも、アトミックな操作を行うConcurrencyクラスの方が効率的であると考えられています。