最近はPHPやGo、JavaScriptばっかりやっていて、ほとんどJavaを触らなくなっていました。
Java9もリリースされたことですし、ここらで少し触っておこうと思います。

今回やってみるのは並行処理です。
以前、仕事で「Javaでバッチ処理」を実装した際、「メインスレッドと別スレッドでバッチを実行させる」という対応をしました。
いわゆる並行処理です。

並行処理とは

1つのCPU上で複数の処理を行うこと(この場合、実行順は保証されない)

並列処理とは

複数のCPUで1つの処理を行うこと(時間のかかる処理を分散させる)

ちなみに、JavaScriptでPromiseやコールバックを使って処理するケースは前者です。
並列処理は計算・演算面で使われるケースが多い印象です。
今回Javaで実装したのは並行処理です。
ExecutorServiceFutureを使います。

  • コード
// 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とか書いてしまい、コンパイラに怒られっぱなしでした。