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

THI05-J. スレッドの強制終了にThread.stop()メソッドを使用しない

スレッドは正常終了できる場合、クラスの不変条件(invariant)を維持する。タスクが完了した場合やリクエストが取り消された場合、プログラムやJVMを早急にシャットダウンしなくてはならない場合には、プログラマがスレッドを強制終了しようとすることが多い。

スレッドの中断、再開および強制終了をサポートするために、いくつかのスレッド管理APIが導入されたが、設計上の欠陥から、後に非推奨(deprecated)とされた。たとえば、Thread.stop()メソッドを呼び出すと、ただちにThreadDeath例外がスローされるが、これは通常、スレッドを停止させる。非推奨メソッドについては、「MET02-J. 非推奨(deprecated)あるいは廃止された(obsolete)クラスやメソッドを使用しない」を参照。

Thread.stop()メソッドの呼出しによりスレッドが獲得したロックはすべて解放されるが、その結果オブジェクトが整合性を欠いた状態で残されるかもしれない。オブジェクトが整合性を欠いた状態になることを防ぐために、スレッドはThreadDeath例外の捕捉とfinallyブロックを使用することができる。しかし、スレッド実行中のいかなる時点においてもThreadDeath例外が発生しうるので、すべてのsynchronizedメソッドおよびsynchronizedブロックを注意深く精査する必要がある。また、catchfinallyブロックの実行中に発生しうるThreadDeath例外からコードを保護しなければならない[Sun 1999]。

セキュリティポリシーファイルからjava.lang.RuntimePermissionstopThreadパーミッションを取り除くことで、Thread.stop()メソッドを使ってスレッドを停止できなくなる。確かにこのアプローチでThread.stop()は利用できなくなるが、これはやってはならない。なぜならば、自前で用意した既存の信頼できるコードは、Thread.stop()メソッドを使うことができると仮定しているかもしれないし、システムは、Thread.stop()メソッドを使えなくしたことで発生するセキュリティ例外を正しく処理できないかもしれないからである。また、使用するサードパーティーのライブラリが、Thread.stop()メソッドに依存している場合も考えられる。

JVMが強制終了された場合のデータ破壊を防ぐ方法については、「ERR09-J. 信頼できないコードにJVMを終了させない」を参照。

違反コード (非推奨の Thread.stop() メソッド)

以下の違反コードに、vector の要素を擬似乱数で満たすスレッドを示す。一定の時間が経過すると、スレッドを強制終了している。

public final class Container implements Runnable {
  private final Vector<Integer> vector = new Vector<Integer>(1000);

  public Vector<Integer> getVector() {
    return vector;
  }

  @Override public synchronized void run() {
    Random number = new Random(123L);
    int i = vector.capacity();
    while (i > 0) {
      vector.add(number.nextInt(100));
      i--;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Container());
    thread.start();
    Thread.sleep(5000);
    thread.stop();
  }
}

Vectorクラスはスレッドセーフである。したがって、Vectorクラスの共有インスタンスが、複数のスレッドによって操作されたとしても、インスタンスは一貫した状態に保たれる。たとえば、ベクターに対する変更が並行して行われたとしても、Vector.size()メソッドは、常に正しい要素の数を返す。ベクターインスタンスは、(あるスレッドが値を変更している途中などのように)その状態が一時的に矛盾した状態におかれている間、自身の固有ロックを取得して他のスレッドからのアクセスを防止するからである。

しかし、Thread.stop()メソッドを呼び出すと、スレッドは現在処理中の動作を停止し、ThreadDeath例外をスローする。また、そのスレッドが取得していたロックはすべて解放される[API 2006]。あるスレッドが、ベクターに整数を新規追加している最中に停止した場合、矛盾した状態のベクターが他のスレッドからアクセス可能になる恐れがある。その場合、ベクターの要素カウントは要素が追加されてからインクリメントされるため、Vector.size()は間違った要素数を返すかもしれない。

適合コード (volatile 変数)

以下の適合コードでは、スレッドを終了するためにvolatile宣言した変数を使用している。shutdown()メソッドは、done 変数をtrueにセットする。スレッドのrun()メソッドはdone変数の値を監視し、値がtrueになると処理を終了する。

public final class Container implements Runnable {
  private final Vector<Integer> vector = new Vector<Integer>(1000);
  private volatile boolean done = false;

  public Vector<Integer> getVector() {
    return vector;
  }

  public void shutdown() {
    done = true;
  }

  @Override public synchronized void run() {
    Random number = new Random(123L);
    int i = vector.capacity();
    while (!done && i > 0) {
      vector.add(number.nextInt(100));
      i--;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Container container = new Container();
    Thread thread = new Thread(container);
    thread.start();
    Thread.sleep(5000);
    container.shutdown();
  }
}
適合コード (割込み可能)

以下の適合コードでは、Thread.interrupt()メソッドをmain()メソッドから呼び出し、スレッドを終了している。Thread.interrupt()メソッドの呼出しにより、割込み状態フラグがセットされる。スレッドは、Thread.interrupted()メソッドを使用して、フラグの値を評価する。Thread.interrupted()メソッドは、Thread.interrupt() メソッドが呼ばれていた場合にはtrueを返し、割込み状態フラグをリセットする。

public final class Container implements Runnable {
  private final Vector<Integer> vector = new Vector<Integer>(1000);

  public Vector<Integer> getVector() {
    return vector;
  }

  @Override public synchronized void run() {
    Random number = new Random(123L);
    int i = vector.capacity();
    while (!Thread.interrupted() && i > 0) {
      vector.add(number.nextInt(100));
      i--;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Container c = new Container();
    Thread thread = new Thread(c);
    thread.start();
    Thread.sleep(5000);
    thread.interrupt();
  }
}

スレッドは、処理の取消しや終了以外のタスクを実行するために、割込みを行うこともあるだろう。したがって、スレッドの割込みポリシーが明確にされている場合にのみ、割込みを行うべきである。さもなければ、割込み要求は失敗する可能性がある。

リスク評価

スレッドの強制停止は、オブジェクトの矛盾した状態を招くおそれがある。要求通りにクリーンアップ処理が実行されない場合、重要なリソースが漏えいする可能性もある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
THI05-J P4 L3
関連ガイドライン
CERT C Secure Coding Standard POS47-C. Do not use threads that can be canceled asynchronously
MITRE CWE CWE-705. Incorrect Control Flow Scoping
参考文献
[API 2006] Class Thread, method stop, interface ExecutorService
[Sun 1999]  
[Darwin 2004] 24.3, Stopping a Thread
[JDK7 2008] Concurrency Utilities, More information: Java Thread Primitive Deprecation
[JPL 2006] 14.12.1, Don't Stop; 23.3.3, Shutdown Strategies
[JavaThreads 2004] 2.4, Two Approaches to Stopping a Thread
[Goetz 2006] Chapter 7, Cancellation and Shutdown
翻訳元

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

THI05-J. Do not use Thread.stop() to terminate threads (revision 145)

Top へ

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