Kodrill

並列処理

Java SE 17 Gold (1Z0-826-JPN) 試験

Thread と Runnable/ExecutorService・Future・Callable/synchronized によるスレッド制御/CopyOnWriteArrayList・ConcurrentHashMap/Flow によるフロー制御

問題数
20 問
合格ライン
65%
実行して確認済み
14 問
この分野の要点を読む6 章・約 20 分。実行して確かめたコード例と、試験でよくある引っかけをまとめています

ログインなしで試す

無料の 5 問を、1問ごとに正誤と解説を見ながら解けます。解答は保存されません。

5 問を試す

無料登録して学習を記録する

学習履歴・ダッシュボード・ブックマーク・模試モードが使えます。

★ 残り 15 問はプレミアム会員限定です

無料プランでは先頭 5 問まで解けます。プレミアムなら全問題・苦手問題の復習・広告非表示が使えます。
料金プランを見る

問題と解説を見る (無料)

登録しなくても、次の5問は問題文と解説をそのまま読めます。

並列処理の要点

内容の確認日: 2026-10-04

この分野では、スレッドの作り方と実行の仕組み、ExecutorService によるタスクの実行、複数のスレッドから同じデータを触るときの排他制御、並列処理向けのコレクション、そして Flow による非同期のデータの受け渡しが問われます。

出題項目は「Thread および Runnable を使用したスレッドの作成」「ExecutorService を使用した並列処理の実装」「synchronized を使用したスレッド制御」「java.util.concurrent パッケージの並列処理コレクションの使用 (CopyOnWriteArrayList および ConcurrentHashMap など)」「java.util.concurrent.Flow を使用したフロー制御」の 5 つです。

Thread と Runnable

スレッドで実行する処理は、Runnable (void run()) として書き、Thread に渡して start() で開始します。start() は新しいスレッドを作ってその中で run() を呼びます。run() を直接呼ぶと、新しいスレッドは作られず、呼び出したスレッドで普通のメソッドとして実行されます。

1 つの Thread オブジェクトの start() は一度しか呼べません。終了したスレッドでも、2 回目の start() は IllegalThreadStateException になります。join() は、そのスレッドが終わるまで待ちます。

Main.java
public class Main {    public static void main(String[] args) throws InterruptedException {        Thread t = new Thread(() -> System.out.println("run: " + Thread.currentThread().getName()), "worker");        t.run();        t.start();        t.join();        t.start();    }}
ターミナル
java Main
run: main
run: worker

実行時に例外が発生: IllegalThreadStateException

ExecutorService と Callable・Future

ExecutorService は、スレッドの管理を任せてタスクを実行する仕組みです。Executors.newFixedThreadPool(n) (n 個のスレッド)・newSingleThreadExecutor() (1 つのスレッドで順番に実行)・newCachedThreadPool() などで作ります。

結果を返すタスクは Callable<V> (V call() throws Exception) で書きます。Runnable と違い、戻り値を返せ、チェック例外も投げられます。submit は Future を返し、Future.get() で結果を受け取ります (終わっていなければ待ちます)。値を返すラムダ式 () -> 6 * 7 は Callable、値を返さないラムダ式は Runnable として扱われます。

invokeAll(タスクのリスト) は、すべてのタスクが終わるまで待ってから、それぞれの Future のリストを返します。shutdown() を呼ぶと新しいタスクを受け付けなくなり (実行中・待機中のタスクは最後まで実行される)、その後の submit は RejectedExecutionException になります。

Main.java
import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;​public class Main {    public static void main(String[] args) throws Exception {        ExecutorService ex = Executors.newFixedThreadPool(2);        try {            Future<Integer> f = ex.submit(() -> 6 * 7);            System.out.println(f.get());            List<Callable<String>> tasks = List.of(() -> "A", () -> "B", () -> "C");            for (Future<String> r : ex.invokeAll(tasks)) {                System.out.print(r.get());            }            System.out.println();        } finally {            ex.shutdown();        }        System.out.println(ex.isShutdown());        ex.submit(() -> 1);    }}
ターミナル
java Main
42
ABC
true

実行時に例外が発生: RejectedExecutionException

invokeAll が返す Future のリストは、渡したタスクの順番と同じです。そのため、タスクがどの順番で終わっても ABC と出力されます。

synchronized による排他制御

n++ は「読み取り・加算・書き込み」の 3 段階の操作なので、複数のスレッドが同時に実行すると更新が失われることがあります。これを防ぐには、synchronized で同時に 1 つのスレッドしか実行できないようにするか、AtomicInteger のような不可分な操作を持つクラスを使います。

synchronized のロックとして使われるオブジェクトは次のとおりです。

・synchronized なインスタンスメソッド: this ・static synchronized なメソッド: クラス名.class ・synchronized (式) { ... } ブロック: 丸括弧に書いたオブジェクト (参照型の式でなければならない)

Main.java
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;​class Counter {    private int n;​    synchronized void increment() {        n++;    }​    synchronized int get() {        return n;    }}​public class Main {    public static void main(String[] args) throws InterruptedException {        ExecutorService ex = Executors.newFixedThreadPool(4);        Counter counter = new Counter();        AtomicInteger atomic = new AtomicInteger();        for (int i = 0; i < 1000; i++) {            ex.submit(() -> {                counter.increment();                atomic.incrementAndGet();            });        }        ex.shutdown();        ex.awaitTermination(10, TimeUnit.SECONDS);        System.out.println(counter.get() + " " + atomic.get());    }}
ターミナル
java Main
1000 1000

wait()・notify()・notifyAll() は、そのオブジェクトのロックを持っているスレッド (つまり、そのオブジェクトの synchronized の中) でしか呼べません。ロックを持たずに呼ぶと、実行時に IllegalMonitorStateException が発生します。

Main.java
public class Main {    public static void main(String[] args) {        Object lock = new Object();        synchronized (lock) {            lock.notify();            System.out.println("synchronized の中では呼べる");        }        lock.notify();    }}
ターミナル
java Main
synchronized の中では呼べる

実行時に例外が発生: IllegalMonitorStateException

並列処理向けのコレクション

java.util.concurrent パッケージには、複数のスレッドから安全に使えるコレクションがあります。

CopyOnWriteArrayList は、変更のたびに内部の配列をコピーして書き込みます。イテレータは作った時点の配列 (スナップショット) をたどるので、拡張 for 文で回している最中に要素を追加しても ConcurrentModificationException は発生せず、追加した要素はそのループには現れません。書き込みのコストが大きい代わりに、読み取りはロックなしで速いので、読み取りが多く書き込みが少ない場面に向いています。

Main.java
import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;​public class Main {    public static void main(String[] args) {        List<Integer> list = new CopyOnWriteArrayList<>(List.of(1, 2, 3));        for (Integer n : list) {            if (n == 1) {                list.add(4);            }            System.out.print(n + " ");        }        System.out.println(list);    }}
ターミナル
java Main
1 2 3 [1, 2, 3, 4]

ConcurrentHashMap は、merge・compute・putIfAbsent などで、1 つのキーに対する「読み取りと更新」をまとめて不可分に行えます。HashMap と違い、キーにも値にも null を入れられず、null を渡すと NullPointerException になります。

Main.java
import java.util.concurrent.ConcurrentHashMap;​public class Main {    public static void main(String[] args) {        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();        map.merge("x", 1, Integer::sum);        map.merge("x", 1, Integer::sum);        map.putIfAbsent("y", 5);        System.out.println(map);        map.put("z", null);    }}
ターミナル
java Main
{x=2, y=5}

実行時に例外が発生: NullPointerException

Flow によるフロー制御

java.util.concurrent.Flow は、データを非同期に受け渡すための 4 つのインタフェースをまとめたものです。

・Publisher: データの発行者。subscribe(Subscriber) で購読者を登録する ・Subscriber: データの購読者。onSubscribe・onNext・onError・onComplete で通知を受け取る ・Subscription: 発行者と購読者の間の購読契約。request(n)・cancel() ・Processor: Subscriber と Publisher を兼ねる中継役

購読者は Subscription.request(n) で「あと n 個まで受け取れる」と要求し、発行者は要求された数しか送りません (背圧制御)。SubmissionPublisher は Publisher の実装で、submit した要素を購読者に非同期で配信し、close() すると全要素の配信の後で onComplete を通知します。

Main.java
import java.util.concurrent.CountDownLatch;import java.util.concurrent.Flow;import java.util.concurrent.SubmissionPublisher;​class Printer implements Flow.Subscriber<String> {    private final CountDownLatch done;    private Flow.Subscription subscription;​    Printer(CountDownLatch done) {        this.done = done;    }​    public void onSubscribe(Flow.Subscription subscription) {        this.subscription = subscription;        subscription.request(1);    }​    public void onNext(String item) {        System.out.println("受信: " + item);        subscription.request(1);    }​    public void onError(Throwable t) {        t.printStackTrace();    }​    public void onComplete() {        System.out.println("完了");        done.countDown();    }}​public class Main {    public static void main(String[] args) throws InterruptedException {        CountDownLatch done = new CountDownLatch(1);        try (SubmissionPublisher<String> publisher = new SubmissionPublisher<>()) {            publisher.subscribe(new Printer(done));            publisher.submit("a");            publisher.submit("b");        }        done.await();    }}
ターミナル
java Main
受信: a
受信: b
完了

配信は別のスレッドで行われるので、main は CountDownLatch で onComplete が呼ばれるまで待っています。待たずに main が終わると、受信の出力が間に合わないことがあります。

試験でよくある引っかけ

  • run() を直接呼ぶ

    run() を直接呼ぶと新しいスレッドは作られず、呼び出したスレッドで実行されます。

  • start() を 2 回呼ぶ

    同じ Thread の start() を 2 回呼ぶと IllegalThreadStateException です。

  • shutdown() の後の submit

    shutdown() の後に submit すると RejectedExecutionException です。実行中のタスクは最後まで実行されます。

  • Runnable と Callable

    値を返し、チェック例外を投げられるのは Callable (call()) です。Runnable の run() は void です。

  • wait・notify の呼び出し場所

    そのオブジェクトのロックを持たずに呼ぶと IllegalMonitorStateException です。

  • ConcurrentHashMap と null

    ConcurrentHashMap はキーにも値にも null を入れられません (NullPointerException)。

問題で確認する

この分野の問題を、解説つきで読めます (登録不要)。

出典・参考

コード例は、試験の対象バージョンで実際にコンパイル・実行して結果を確認しています。