Thread.yield(), interrupt(), isInterrupted(), and interrupted() Behavior Explained

中断机制において、Javaの次の3つのメソッドは異なる動作をします:

  • interrupt(): 中断リクエストを送る。スレッドが待機中(sleep、wait、join)라면InterruptedException をスローし、中断フラグをクリアする。実行中またはLockSupport.park()中のスレッドであれば、中断フラグのみをセットする。
  • isInterrupted(): 中断フラグの状態を確認するが、フラグを変更しない。
  • interrupted(): 現在のスレッドの中断フラグを確認し、その後そのフラグをクリアする。
  1. 実行中のスレッドへの中断要求
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RunningThreadInterruptExample {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                // 効率的ブロッキングのため、本体処理のみ
            }
            log.info("Worker thread detected interruption and exited loop.");
        }, "worker");

        worker.start();

        // アドレス有効化のため、少し遅延
        TimeUnit.MILLISECONDS.sleep(50);

        log.info("Before interrupt: {} — {}", worker.getName(), worker.isInterrupted());
        worker.interrupt();
        log.info("After interrupt: {} — {}", worker.getName(), worker.isInterrupted());

        // ループ終了後の動き確認のため
        TimeUnit.MILLISECONDS.sleep(100);
    }
}

このケースでは、スレッドは中断要求を検知するとループを脱出し、自然に終了する。中断そのものは強制停止ではなく、あくまで「検出可能な状態変化」である。

  1. 待機状態(sleep)のスレッドへの中断要求
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SleepingThreadInterruptExample {
    public static void main(String[] args) throws InterruptedException {
        Thread sleeper = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(500);
            } catch (InterruptedException e) {
                log.warn("Sleep was interrupted: {}", e.getClass().getSimpleName());
                // スレッドはここで例外を捕捉し、終了 path を選択
                return;
            }
            log.info("Completed sleeping successfully.");
        }, "sleeper");

        sleeper.start();
        TimeUnit.MILLISECONDS.sleep(100);
        log.info("Pre-interrupt flag: {}", sleeper.isInterrupted());
        sleeper.interrupt();
        TimeUnit.MILLISECONDS.sleep(100);
        log.info("Post-interrupt flag: {}", sleeper.isInterrupted());
    }
}

interrupt()でスリープ中スレッドを起こすと、InterruptedExceptionが発生し、中断フラグは自動的にfalseクリアされるため、再読み取り時にfalseとなる。

  1. 中断フラグ操作メソッドの内部構造
// 非同期終了判定専用インスタンスメソッド(フラグ変更なし)
public boolean isInterrupted() {
    return isInterrupted(false); // ClearsInterrupted = false
}

// スタティックメソッド。現在のスレッドのみ対象で、フラグをクリア
public static boolean interrupted() {
    return currentThread().isInterrupted(true); // ClearsInterrupted = true
}

// 実際にフラグ操作を行うネイティブ実装
private native boolean isInterrupted(boolean clearFlag);

注目すべきは、interrupted()が常に受信スレッド自身のフラグを確認する点であり、書名通り「現在のスレッド」に限定されることである。これは祖先スレッドの中断状態を参照したい場合に誤用されがちな落とし穴である。

  1. 最初に中断リクエストを返す設計的重要性

特別な処理なしに外部スレッドが強制的に中断すると、共有リソースの不整合やリソースリークを招きやすい。したがって、ベストプラクティスとしては、スレッド自体が中断状態をポーリングし、適切なクリーンアップと早期終了パスを自ら選択するようにすべきである。

たとえば、以下のように中断確認と状態遷移を明示的に構成できる:

public void run() {
    while (true) {
        if (Thread.interrupted()) {
            // フラグクリアしつつ、中断フラグの存在を検出
            handleInterruption();
            return;
        }
        performTaskStep();
    }
}

タグ: Java Thread interrupt concurrency synchronization

8月31日 00:45 投稿