第37回nds java並行処理 今昔物語
DESCRIPTION
Javaの並列・並行処理について、標準ライブラリのユーティリティなどをピックアップして紹介TRANSCRIPT
Java 並行処理 今昔物語NDS37 @civic
今回話す内容
• Java の並行処理モデルについて
• 並行処理・非同期処理実行方法
• スレッドセーフのために
こちらを参考にピックアップ
• Java並行・並列・非同期処理チートシート - Qiita
• http://qiita.com/yohhoy/items/bc119324d2b69570597b
Javaの並行処理モデルについて
並行処理モデル
• スレッドモデル
• 共有データ
• ロック
concurrent関係 歴史年表
歴史年表Version Class/ Package / Syntax Year1.0 Thread / synchronized `961.2 Synchronized Collection `98
ThreadLocal1.3 Timer `005 ConcurrentHashMap `04
Semaphore, Lock …java.util.atomic
7 Fork / Join `118 parallelStream `14
並行処理・非同期処理 実行方法あれこれ
Thread
Java 1.0 から。Runnable インターフェースを実装。Threadの生成、開始、排他制御、同期は自分でやらなければならない。
Thread
Thread th = new Thread(new Runnable(){ public void run(){ //このスレッドでの処理 } }); th.start(); //スレッドの実行開始
ちなみにJava8なら
Thread th = new Thread(()->{ //このスレッドでの処理 }); th.start(); //スレッドの実行開始
Synchronized
Java 1.0 から。Threadでの排他制御。言語として採用されている。
Synchronizedpublic synchronized void add(){ //メソッド全体 //このメソッドを同時に実行できるのは //1スレッドのみ } !Object lock = new Object(); synchronized(lock){ //ロックオブジェクト //とブロック // ... }
TimerJava 1.3から。バックグラウンドでの遅延実行。単一のスレッドが割り当てられ、指定時間後または一定間隔にタスクを実行する。
javascriptのsetTimeout, setInterval的な。
単一スレッドで実行するのでタスクの実行に時間がかかるとスレッドを専有し、後続のタスクの実行に影響する。
Timer
Timer timer = new Timer(); timer.schedule(new TimerTask(){ public void run(){ // ... } }, 1000, 5000); //1秒後に開始、5秒間隔
Executor
Java5から。別スレッドでのタスク実行をExecutorServiceに登録して実行。ExecutorServiceはシングルスレッドだったり固定数のスレッドプールだったりできる。
Futureを使うことで、別スレッドでのタスク実行結果を受け取るのが楽になる。
ExecutorExecutorsService es = Executors.newSingleThreadExecutor(); Future<Integer> future = es.submit(new Callable<>(){ // ... return ret; }); !Integer ret = future.get(5, TimeUnit.SECONDS); //タスクの終了までブロック( 大5秒)
ExecutorsServiceの実装ExecutorsServiceの実装を取り替えることで、タスク実行スレッドを変化させることができる。 !ExecutorsService es = Executors.newFixedThreadPool(3); //3スレッドでタスク消化 !ExecutorsService es = Executors.newCachedThreadPool(); //必要に応じて新規スレッド生成・廃棄
Fork/JoinJava7から。Executorsよりも、細粒度のタスクを実行する場合にExecutorsよりも高速。
ForkJoinPoolに、RecursiveTask(返り値あり)またはRecursiveAction(返り値なし)を登録して並列に実行する。
I/O処理中心のタスクよりも、CPU処理中心のタスクを実行する場合に効果的。
Fork/Joincompute(){ if (作業.サイズ < しきい値){ return doWork(作業); } else { f1 = fork(分割した作業の前半); f2 = fork(分割した作業の後半); 2つのfork処理がjoinするまで待機 } }
parallelStream
Java8から。streamを並列処理する。内部的にFork/Joinを使っている。
parallelStreamint total = IntStream.range(1, 1000) .parallel() .sum(); !myArraylist.parallelStream().forEach((elm)->{ //各要素の処理 }); !※java8 のConsumerインターフェースのラムダ式
スレッドセーフのために
Synchronized Collection
Java1.2から。 複数のスレッドで安全にデータを操作できるCollection
ArrayList, HashMapなどは複数のスレッドから触ると意図しないデータ操作になりかねない。
Synchronized Collection
List<String> syncList = Collections.synchronizedList(new ArrayList<>()); !Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
ConcurrentHashMap
Java5から。java.util.concurrent.ConcurrentHashMapは、スレッドセーフでありながら、Collections.synchronizedMapよりも高い更新平行性をサポート。
CopyOnWriteArrayList
Java6から。java.util.concurrent.CopyOnWriteArrayListは、スレッドセーフ。イテレーションもロックされない。
変更操作のたびに新しいコピーが生成される。変更がほとんどなくイテレーションが多い操作に有効。
AtomicInteger
Java5から。
AtomicInteger、AtomicLongのようなクラスはスレッドセーフでロックフリーな変数として使用できる。
AtomicInteger
AtomicInteger i = new AtomicInteger(1); i.incrementAndGet(); //i++
シンクロナイザ
java5から。マルチスレッドでの同期方法をサポートするためのユーティリティ。
いろいろある。Semaphore, CountDownLatch, CyclicBarrier, Phaser, Exchanger...
CountDownLatchCountDownLatch latch = new CountDownLatch(3); for (int n = 0; n < 3; n++){ Thread th = new Thread(() -> { // ... なんらかの処理 latch.countDown(); }); th.start(); } latch.await(2, TimeUnit.SECONDS); //latchが0になるのを 大2秒wait
Semaphore
• 同時実行可能なスレッドを制限
CyclicBarrier
• CountDownLatch のように待ち合わせ
• 解放後に再利用できる循環式
Exchanger
• 2つのスレッド間でデータ交換
まとめ• 並行処理・非同期処理の実行方法
• Thread, Timer, Executor, Fork/Join
• 非同期処理のためのユーティリティ
• java.util.concurrent
• https://github.com/civic/nds-java-concurrent