Yomiage
メニュー

Java実践講座 14:スレッド・仮想スレッド・共有状態

更新 2026/9/27 6:22:11 公開

投稿者: しゅりりん

到達点は、並行処理の必要性と競合の原因を説明することです。前提は第08・13回です。

同時進行と同時実行

並行とは複数の作業を進める構造、並列とは複数の計算を実際に同時実行することです。ネットワーク待ちの多い処理では、待っている間に別の仕事を進める価値があります。Java 21の仮想スレッドは、このような多数の待ちを伴う仕事で候補になります。CPUそのものを増やす機能ではありません。

import java.util.ArrayList;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) throws Exception {
        AtomicInteger count = new AtomicInteger();
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var futures = new ArrayList<Future<?>>();
            for (int i = 0; i < 20; i++) {
                futures.add(executor.submit(() -> {
                    for (int j = 0; j < 1000; j++) count.incrementAndGet();
                }));
            }
            for (Future<?> future : futures) future.get();
        }
        System.out.println(count.get());
    }
}

Main.javaの出力は20000です。Future.getで仕事の完了と失敗を確認します。これは共有カウンタの実習であり、仮想スレッドの速度を測るベンチマークではありません。

足し算でも競合する

通常の count++ は、読む、増やす、書くという複数の段階からなります。二つのスレッドが同じ古い値を読むと、一方の増分が失われます。AtomicIntegerは今回の一回の加算を原子的に行います。volatileだけでは、この複合操作を原子的にはできません。

複数の値をまとめて守るなら、同じロックで囲むsynchronizedなどが必要です。「在庫があるか確認」と「在庫を減らす」を別々に同期しても、その間へ他の処理が入れます。ロックの対象は変数ではなく、守りたい一連の操作から決めます。

外部サービスへ大量の仕事を投げる場合は、スレッドが軽量でも接続数や同時実行数を制限します。中断要求を受けたときは、適切に伝播するか割り込み状態を戻します。停止を無視すると、タイムアウト後も処理が残ります。

練習と解答

練習:AtomicIntegerを普通のint配列の先頭要素に変え、毎回20000にならない可能性を説明します。

解答:共有値への更新が競合します。何度か20000になっても正しさの証明にはなりません。修正案はAtomicInteger、同じロックによる保護、各タスクで局所集計して最後に合算する方法です。

公式資料

仮想スレッドと並行処理APIが参照先です。


Java全20回の目次 | 前の回 | 次の回

このブラウザでは Web Speech API が利用できません。

1 / 16 · 停止中