スレッドの開始方法は誤解しやすい。スレッドで実行したい処理をコード上は正しく実行しているように見えても、実際には間違ったスレッドによって実行してしまっていることがある。Thread.start()メソッドを呼び出すと、Javaの実行環境は新たに開始したスレッドの上で、そのスレッドのrun()メソッドを実行する。Threadオブジェクトのrun()メソッドを直接呼び出すのは間違いである。直接呼び出した場合、run()メソッドのなかに書かれた処理は、新規に生成されたスレッドではなく、呼出し元のスレッドにより実行されてしまう。また、Threadオブジェクトが、Runnableオブジェクトから生成されるのではなく、run()メソッドをオーバーライドしていないThreadのサブクラスをインスタンス化することによって生成される場合、サブクラスのrun()メソッドはThread.run()メソッドを呼び出すため、何の処理も実行されない。したがって、Thread オブジェクトの run() メソッドをプログラムから直接呼び出してはならない。
違反コード
以下の違反コードでは、実行中のスレッドがrun()メソッドを明示的に呼び出している。
public final class Foo implements Runnable { @Override public void run() { // ... } public static void main(String[] args) { Foo foo = new Foo(); new Thread(foo).run(); } }
run()メソッドが新規に作成されたスレッドの実行を開始するとプログラマは想定しているが、それは間違いであり、新規スレッドの実行が開始されることはない。その結果、run()メソッドの中に書かれた処理は、新しいスレッドではなく、呼出し元スレッドにおいて実行される。
適合コード
以下の適合コードではstart()メソッドを正しく呼び出しており、Java の実行環境は新規スレッドの実行を開始する。
public final class Foo implements Runnable { @Override public void run() { // ... } public static void main(String[] args) { Foo foo = new Foo(); new Thread(foo).start(); } }
例外
THI00-EX0: 単体テストを行うコードでは、run()メソッドを直接呼び出してもよい。ただし、このテスト手法は、マルチスレッド環境での使用を想定したテストには使用できない。
Runnableオブジェクトを引数に構築されたThreadオブジェクトに対してThread.run()メソッドを実行する場合、ThreadオブジェクトをRunnableにキャストすることで、コード解析による検出を回避できる。
public void sampleRunTest() { Thread thread = new Thread(new Runnable() { @Override public void run() { // ... } }); ((Runnable) thread).run(); // THI00-EX0: 新規スレッドを開始しない }
Runnableにキャストしてからrun()メソッドを呼び出すことで、Thread.run()メソッドの明示的な呼出しを意図的に行っていることが明らかになる。また、このことをコメントとして残すことも強く推奨される。
THI00-EX1: 新規スレッドを開始するJava実行環境のシステムコードから Threadオブジェクトのrun()メソッドを直接呼び出すのは、Javaの実行環境には当然必要な動作であるため、構わない。しかし、ユーザプログラムにこの例外を適用することはほとんどないであろう。
リスク評価
スレッドの実行を正しく開始しないと、予期せぬプログラムの動作を引き起こす場合がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
THI00-J | 低 | 中 | 中 | P4 | L3 |
自動検出
Thread.run()を直接呼び出すコードを検出するのは容易であるが、どの呼び出しがこのルールの例外に該当するか自動判別することは困難である。ヒューリスティックなアプローチは役に立つであろう。
関連ガイドライン
MITRE CWE | CWE-572. Call to Thread run() instead of start() |
参考文献
[API 2006] | Interface Runnable and class Thread |
翻訳元
これは以下のページを翻訳したものです。
THI00-J. Do not invoke Thread.run() (revision 88)