最近はPHPやGo、JavaScriptばっかりやっていて、ほとんどJavaを触らなくなっていました。
Java9もリリースされたことですし、ここらで少し触っておこうと思います。
今回やってみるのは並行処理です。
以前、仕事で「Javaでバッチ処理」を実装した際、「メインスレッドと別スレッドでバッチを実行させる」という対応をしました。
いわゆる並行処理です。
並行処理とは
1つのCPU上で複数の処理を行うこと(この場合、実行順は保証されない)
並列処理とは
複数のCPUで1つの処理を行うこと(時間のかかる処理を分散させる)
ちなみに、JavaScriptでPromise
やコールバックを使って処理するケースは前者です。
並列処理は計算・演算面で使われるケースが多い印象です。
今回Javaで実装したのは並行処理です。ExecutorService
とFuture
を使います。
- コード
// Main.java
import java.util.*;
import java.util.concurrent.*;
public class Main {
/**
* 特定数のスレッドを生成し、すべての結果をまって処理を終了する
*/
public static void main(String[] args) throws Exception {
// スレッドプールの生成
ExecutorService service = Executors.newFixedThreadPool(8);
// ランダムな秒数をタスクに追加する
Random rand = new Random();
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int count = i + 1;
int millis = rand.nextInt(10) * 1000;
tasks.add(new Tasks(count, millis));
}
try {
long start = System.currentTimeMillis();
// タスクを一括実行
List<Future<String>> futures = service.invokeAll(tasks);
for (Future<String> future : futures) {
System.out.println(future.get());
}
long end = System.currentTimeMillis();
long term = (end - start);
System.out.println(String.format("メイン処理終了 - %dミリ秒", term));
} catch (InterruptedException e) {
System.out.println(e.getMessage());
throw e;
} finally {
if (service != null) {
service.shutdown();
}
}
}
}
/**
* タスク
*/
class Tasks implements Callable<String> {
private int idx;
private int millis;
public Tasks(int idx, int millis) {
this.idx = idx;
this.millis = millis;
}
@Override
public String call() throws Exception {
long start = System.currentTimeMillis();
// 指定時間待機
Thread.sleep(millis);
long end = System.currentTimeMillis();
long term = (end - start);
Thread current = Thread.currentThread();
return String.format("%d件目の処理 - %dミリ秒 - スレッド【%s】", idx, term, current.getName());
}
}
- 結果
$ javac -encoding utf8 Main.java && java Main
1件目の処理 - 2001ミリ秒 - スレッド【pool-1-thread-1】
2件目の処理 - 6010ミリ秒 - スレッド【pool-1-thread-2】
3件目の処理 - 5011ミリ秒 - スレッド【pool-1-thread-3】
4件目の処理 - 0ミリ秒 - スレッド【pool-1-thread-4】
5件目の処理 - 5010ミリ秒 - スレッド【pool-1-thread-5】
6件目の処理 - 5010ミリ秒 - スレッド【pool-1-thread-6】
7件目の処理 - 5010ミリ秒 - スレッド【pool-1-thread-7】
8件目の処理 - 8010ミリ秒 - スレッド【pool-1-thread-8】
9件目の処理 - 3000ミリ秒 - スレッド【pool-1-thread-4】
10件目の処理 - 5010ミリ秒 - スレッド【pool-1-thread-1】
メイン処理終了 - 8021ミリ秒
一番処理に時間がかかっているタスク(8件目)とほぼ同じ時間でメイン処理が終了したことがわかります。
また、出力内容からスレッドが使いまわされていることもわかります。
いかがでしたでしょうか。
意外と簡単に実装できたことと、このjava.util.concurrent
パッケージはJavaSE6からあったことが衝撃でした。
ちなみに、GoならGoroutine
という機能があるので、もっと簡単に実装できます。
※ただし、プロセスやスレッドとは厳密には異なるため、アプローチや考え方が少し変わります。
余談として、内容的に大したプログラムではありませんが、メソッド宣言でfunction
と書きそうになったり、変数宣言でval
とか書いてしまい、コンパイラに怒られっぱなしでした。