エキサイト株式会社 メディア開発のしばたにえんです。
さっそくですが、下記のコードをご覧ください
class CountTest {
private int num = 0;
@SneakyThrows
void CountThreadNum() {
int threadNum = 10;
ExecutorService service = Executors.newFixedThreadPool(threadNum);
for (int n = 0; n < threadNum; n++) {
service.submit(() -> {
for (int m = 0; m < 1000; m++) {
num++;
}
});
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(num);
}
}
スレッドを10個でそれぞれ1000回ずつ1を加算していくといった処理になりますが、
この結果は10000にはなりません。
intはアトミックではないためです。
概要
そもそもアトミックとは
コンピュータ上のプログラムの動作で、密接に関連する複数の処理が外部から一つの操作に見え、途中の状態を観測したり介入できない性質を、操作のアトミック性、不可分性などという。
要するに
複数のスレッドからのデータを書き込んでも最終的な値を保証しているというものです。
上記の問題を解決するのはAtomicIntegerです。
AtomicIntgerは多くのスレッドを同時に使用できるIntegerクラスです。
実際に使ってみます
class CountTest {
private AtomicInteger atomicInteger = new AtomicInteger();
@SneakyThrows
void CountThreadAtomicIntegerNum() {
int threadNum = 10;
ExecutorService service = Executors.newFixedThreadPool(threadNum);
for (int n = 0; n < threadNum; n++) {
service.submit(() -> {
for (int m = 0; m < 1000; m++) {
atomicInteger.incrementAndGet();
}
});
}
service.shutdown();
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(atomicInteger.get());
}
}
期待されている値が取れました。
使い方
AtomicIntegerの使い方は主に二つあります
①カウンターとしての役割をアトミック性を保ちながら行う
②比較処理をアトミック性を保ちながら行う
①
1ずつ加算する場合
void atomicIntegerIncrementAndGet() {
final List<String> characters = List.of("a", "b", "c", "d", "e", "f", "g", "h");
final AtomicInteger atomicInteger = new AtomicInteger();
characters.stream().forEach(
charcter -> System.out.println(
String.format("%s:%s", charcter, atomicInteger.incrementAndGet())
)
);
}
初期値を入れて1ずつ加算する
void atomicIntegerIncrementAndGet() {
final List<String> characters = List.of("d", "e", "f", "g", "h");
final AtomicInteger atomicInteger = new AtomicInteger(3);
characters.stream().forEach(
charcter -> System.out.println(
String.format("%s:%s", charcter, atomicInteger.incrementAndGet())
)
);
}
5ずつ加算する
void atomicIntegerGetAndAdd() {
final AtomicInteger atomicInteger = new AtomicInteger();
for (int n = 0; n < 3; n++) {
System.out.println(atomicInteger.getAndAdd(5));
}
}
②
比較
void atomicIntegerIncrementAndGetCompare() {
final List<String> characters = List.of("a", "b", "c", "d", "e", "f", "g", "h");
int targetNum = 3;
final AtomicInteger atomicInteger = new AtomicInteger();
characters.stream().forEach(
charcter -> {
if (atomicInteger.compareAndSet(targetNum, atomicInteger.incrementAndGet())) {
System.out.println(
String.format("%s:%s", charcter, atomicInteger.get())
);
}
}
);
}
まとめ
マルチスレッドの場合ちょっとしたループ内の加算処理でも上記のようなずれが起きてしまいます。
AtomicIntegerちゃんと使っていきましょう!