エキサイト株式会社 メディア開発のしばたにえんです。
さっそくですが、下記のコードをご覧ください
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); } } // 9818
スレッドを10個でそれぞれ1000回ずつ1を加算していくといった処理になりますが、 この結果は10000にはなりません。 intはアトミックではないためです。
概要
そもそもアトミックとは
コンピュータ上のプログラムの動作で、密接に関連する複数の処理が外部から一つの操作に見え、途中の状態を観測したり介入できない性質を、操作のアトミック性、不可分性などという。
要するに 複数のスレッドからのデータを書き込んでも最終的な値を保証しているというものです。
上記の問題を解決するのはAtomicIntegerです。 AtomicIntgerは多くのスレッドを同時に使用できるIntegerクラスです。
実際に使ってみます
class CountTest { // private int num = 0; 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()); } } // 10000
期待されている値が取れました。
使い方
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()) ) ); } // a:1 // b:2 // c:3 // d:4 // e:5 // f:6 // g:7 // h:8
初期値を入れて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()) ) ); } // d:4 // e:5 // f:6 // g:7 // h:8
5ずつ加算する
void atomicIntegerGetAndAdd() { final AtomicInteger atomicInteger = new AtomicInteger(); for (int n = 0; n < 3; n++) { System.out.println(atomicInteger.getAndAdd(5)); } } // 0 // 5 // 10
② 比較
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()) ); } } ); } // c:3
まとめ
マルチスレッドの場合ちょっとしたループ内の加算処理でも上記のようなずれが起きてしまいます。 AtomicIntegerちゃんと使っていきましょう!