JPCERT コーディネーションセンター

TPS02-J. スレッドプールにサブミットするタスクは割込み可能にする

自身をシャットダウンしたりスレッドプール中の個々のタスクを取消す機能を必要とするスレッドプールにタスクをサブミットする場合には、Thread.interrupt() による割込み可能なタスクのみをサブミットしなければならない。Java APIインタフェースでは、java.util.concurrent.ExecutorService.shutdownNow() メソッドについて以下のように説明している[API 2006]。

実行中のアクティブなタスクすべての停止を試み、待機中のタスクの処理を停止し、実行を待機していたタスクのリストを返す。 実行中のアクティブなタスク処理を停止するために最善の努力をすること以上の保証はない。たとえば、通常の実装では Thread.interrupt() を介して取消しが行われるため、割り込みに応答しないタスクは終了しない可能性がある。
違反コード (スレッドプールの終了)

以下の違反コードでは、PoolService クラス内で宣言されたスレッドプールに、SocketReader クラスをタスクとしてサブミットしている。

public final class SocketReader implements Runnable { // スレッドセーフなクラス
  private final Socket socket;
  private final BufferedReader in;
  private final Object lock = new Object();

  public SocketReader(String host, int port) throws IOException {
    this.socket = new Socket(host, port);
    this.in = new BufferedReader(
        new InputStreamReader(this.socket.getInputStream())
    );
  }

 //一度に1つのスレッドだけがソケットを使用できる
  @Override public void run() {
    try {
      synchronized (lock) {
        readData();
      }
    } catch (IOException ie) {
   // ハンドラへの転送
    }
  }

  public void readData() throws IOException {
    String string;
    try {
      while ((string = in.readLine()) != null) {
    // ストリームがnullになるまでブロックする
      }
    } finally {
      shutdown();
    }
  }

  public void shutdown() throws IOException {
    socket.close();
  }
}

public final class PoolService {
  private final ExecutorService pool;

  public PoolService(int poolSize) {
    pool = Executors.newFixedThreadPool(poolSize);
  }

  public void doSomething() throws InterruptedException, IOException {
    pool.submit(new SocketReader("somehost", 8080));
    // ...
    List<Runnable> awaitingTasks = pool.shutdownNow();
  }

  public static void main(String[] args) 
                          throws InterruptedException, IOException {
    PoolService service = new PoolService(5);
    service.doSomething();
  }
}

このコード例のタスクは Thread.interrupt() メソッドを用いた割込みに対応しておらず、また、shutdown() メソッドは、実行中のタスクがすべて終了するまで待機状態となるため、shutdownNow() メソッドがスレッドプールを終了する保証はない。

同様に、スレッドを終了するタイミングを判定するために Thread.interrupted() メソッド以外の仕組みを使用するタスクは、shutdown()shutdownNow() メソッドに応答しないであろう。たとえば、終了することが安全かどうか判定するために volatile フラグをチェックするタスクは、これらのメソッドに応答しない。フラグを使ったスレッド終了に関する詳細は、「THI05-J. スレッドの強制終了にThread.stop()メソッドを使用しない」で説明している。

適合コード (割込み可能なタスクをサブミットする)

以下の適合コードでは、割込みに対応した SocketReader クラスを定義し、インスタンスをつくってスレッドプールにサブミットしている。

public final class SocketReader implements Runnable {
  private final SocketChannel sc;
  private final Object lock = new Object();

  public SocketReader(String host, int port) throws IOException {
    sc = SocketChannel.open(new InetSocketAddress(host, port));
  }

  @Override public void run() {
    ByteBuffer buf = ByteBuffer.allocate(1024);
    try {
      synchronized (lock) {
        while (!Thread.interrupted()) {
          sc.read(buf);
          // ...
        }
      }
    } catch (IOException ie) {
   // ハンドラへの転送
    }
  }
}

public final class PoolService {
  // ...
}
例外

TPS02-EX0: 待ち状態にならずに短時間で完了するタスクは、このルールに従う必要はない。

リスク評価

割込み可能でないタスクをサブミットすると、スレッドプールの終了が不可能となり、サービス停止状態を引き起こす危険がある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
TPS02-J P4 L3
参考文献
[API 2006] interface ExecutorService
[Goetz 2006a] Chapter 7, Cancellation and Shutdown
翻訳元

これは以下のページを翻訳したものです。

TPS02-J. Ensure that tasks submitted to a thread pool are interruptible (revision 56)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter