Java 入門

ホーム > マルチスレッド > ExecutorService での処理のキャンセル方法

ExecutorService での処理のキャンセル方法

通常わざわざスレッドを分けて行うタスクは、その処理に時間がかかるものです。

逆に、常に即座に処理が終わるようなタスクをわざわざスレッドを分けて行うのは適切ではありません。 (呼ばれる側の実装がわからず、念のため呼び出し側をシリアライズするために別スレッドに処理を投げる、、なんてこともありますけど、 いずれにせよ喜ばしい状況ではありませんよね)

時間のかかる処理は途中でキャンセルしたい場合もあるでしょう。

処理をキャンセルしたい場合、どうしたらよいでしょうか。ExecutorService ではタスクのキャンセルもできるように設計されています。

ここでは ExecutorService での処理のキャンセル方法について説明します。

処理のキャンセルの仕方

ExecutorService の使い方 で説明したように Executor によって処理されるタスクの状態は次の四つです。

ExecutorService - タスクの状態

タスクを submit または execute メソッドでサブミットしますが、スレッドプール内のワーカースレッドが全てビジー (他のタスクの処理をしている状態)であれば、サブミットしても処理は直ちには開始しません。

その場合はタスクはブロッキングキューに submitted 状態のまま入ります。

さて、ExecutorService の submit メソッドで処理をサブミットすると、その戻り値として Future オブジェクトが返ります

Future オブジェクトは非同期処理の結果を表します。

Future オブジェクトの cancel メソッドに true を渡して呼び出すと、 そのタスクの処理はキャンセルされます。

まだ開始していない、キューに入っているタスクはキャンセルすると、直ちにキューから削除されます。

一方、処理が開始した場合 (started 状態のタスク) はキャンセルを試みても、直ちに処理が中断するのではなく、スレッドが INTERRUPTED 状態になります。

従って、 タスクの run メソッドの中で、 Thread.interrupted メソッドで実行中のスレッドが interrupted 状態になっていないかチェックし、 キャンセルが要求された場合は InterruptedException を投げて処理を中断するなどします。

処理を中断する具体例

では簡単なサンプルコードで実際に、処理を中断してみましょう。

package com.keicode.java.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadTest5 {
  
  public static void main(String[] args) {
    
    System.out.println("Entering main");
    
    Future future = null;
    ExecutorService exec = Executors.newSingleThreadExecutor();
      
    try {
      for(int i=0;i<10; i++){
        
        Thread.sleep((long)(Math.random() * 100));
        
        if(future != null){
          future.cancel(true);
        }
        
        future = exec.submit(new Runnable(){
          public void run() {
            try {
              if(Thread.interrupted()){
                throw new InterruptedException();
              }
              
              Thread.sleep(1 * 1000);
              
              if(Thread.interrupted()){
                throw new InterruptedException();
              }
              
              System.out.println("Hello! - " 
                + Thread.currentThread().getId());
            
            } catch (InterruptedException e) {
              System.out.println("Interrupted - " 
                + Thread.currentThread().getId());
            }
          }
        });
      }
        
      System.out.println("Sleeping..." 
        + Thread.currentThread().getId());      
      Thread.sleep(15 * 1000);
        
    } catch(InterruptedException e) {
      e.printStackTrace();
    }
  
    System.out.println("Exit main");
      
  }

}

この実行結果は次のようになりました。

Entering main
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Interrupted - 8
Sleeping...1
Hello! - 8
Exit main

この例ではタスクを submit メソッドでサブミットしています。その戻り値として、 Future オブジェクトを取得しています。

Future オブジェクトの cancel メソッドを呼び、処理をキャンセルしています。

run 内で少し (ランダムな時間) 待っていますが、こうすることで例外が投げられた様子をみられるようになります。 私が試した限り、待ちを入れないと例外が投げられる様子は確認できません(もちろん処理もされません)。 これはタスクがブロッキングキューの中に入っているうちにキャンセルされたため、 run メソッドが全く呼び出されないためです。

ホーム > マルチスレッド > ExecutorService での処理のキャンセル方法