Revised: Sep./01th/2004
スレッドはオブジェクトとして作成され、実行されます。その方法は二つあります。
java.lang.Thread クラスの継承java.lang.Runnable メソッドの実装作り方と考え方が異なりますので、別々に説明します。
Thread クラスの継承スレッドを新しく定義する場合は、次のようにします:
Thread クラスを継承したクラスを作るrun() メソッドをオーバーライドするstart() メソッドを呼び出す一般に、スレッドは Thread クラスかそのサブクラスのオブジェクトとして生成され、実行されます。実際に実行される処理は run() メソッドであり、自分でスレッドを作る場合は、 run() メソッドをオーバーライドして、目的の処理を記述することになります。
このクラスをインスタンス化して生成されたスレッドは、 start() メソッドによって、実行可能状態 (Runnable state) になり、マシンのリソースが割り当てられるのを待機します。そして、実際に割り当てられて実行中 (running) になったときに実行される制御が run() メソッドに記述されていることになります。
CountTest.java:
class Count extends Thread { public void start() { System.out.println("** Runnable ****"); super.start(); } public void run() { System.out.println("** running *****"); for (int i=0; i <= 5; i++) { System.out.println("Count: " + i); } System.out.println("** Dead ********"); } } class CountTest { public static void main(String[] args) { Count thread = new Count(); System.out.println("** New Thread **"); thread.start(); System.out.println("last line in main()"); } }
スレッドの状態を表示する為に start() メソッドをオーバーライドしていますが余分な作業です。
C:\Java\Thread>javac CountTest.java C:\Java\Thread>java CountTest ** New Thread ** ** Runnable **** last line in main() ** running ***** Count: 0 Count: 1 Count: 2 Count: 3 Count: 4 Count: 5 ** Dead ******** C:\Java\Thread>
スレッドが生成されてから実行終了されるまでの流れが確認できます。特に、 main() メソッドでスレッドが作られて start() が呼ばれてから直ぐに run() が実行されているわけではないことに注目してください。このマシンでは、 main() の制御が CPU を握っており、この制御が終了して初めて、新しく作ったスレッドに CPU が割り当てられて run() が実行されています。
Runnable インタフェースの実装Java は一つのスーパークラスしかもてない単一継承の言語です。複数のクラスを継承することはできません。したがって、あるクラスを継承しているサブクラスをスレッドとして実行したい場合は、 Thread クラスの継承として作るわけには行きません。
このような場合は、 Runnable インタフェースを実装します。実は Thread クラスも Runnable インタフェースの実装クラスに他なりません。全てのスレッドクラスは Runnable インタフェースの実装クラスです。
Runnable インタフェースを実装するrun() メソッドを実装するThread クラスをインスタンス化するstart() メソッドを呼び出す実装クラスをインスタンス化して作ったオブジェクトの参照を、 Thread クラスのコンストラクタ引数にします。
自分が作るスレッドが、 run() メソッドの実装以外になにもしなくても良い場合は、不必要なサブクラス化を避ける為に Runnable インタフェースの実装として作るべきです。
次のサンプルは、二つのスレッドを作って実行しています。
CountsTest.java:
class CountA implements Runnable { public void run() { for (int i=0; i <= 5; i++) { System.out.println("A: " + i); } } } class CountB implements Runnable { public void run() { for (int i=5; i >= 0; i--) { System.out.println(" B: " + i); } } } class CountsTest { public static void main(String[] args) { // ランナブルクラスのインスタンス化 CountA runA = new CountA(); CountB runB = new CountB(); System.out.println("Runnable Class のインスタンス化終了"); // スレッドのインスタンス化 Thread threadA = new Thread(runA); Thread threadB = new Thread(runB); System.out.println("Thread へ受け渡し終了"); // スレッドの開始 threadA.start(); threadB.start(); System.out.println("Thread の start() 終了"); System.out.println("適時自動的に run() が実行される"); } }
実行例:
C:\Java\Thread>javac CountsTest.java
C:\Java\Thread>java CountsTest
Runnable Class のインスタンス化終了
Thread へ受け渡し終了
Thread の start() 終了
適時自動的に run() が実行される
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
B: 5
B: 4
B: 3
B: 2
B: 1
B: 0
C:\Java\Thread>
この実行例では、メインスレッド、スレッド A 、スレッド B が順番に実行されているだけなので、面白くありません。各スレッドは独立に実行されているので、実行マシンの構成如何によっては、次のように表示される可能性もあります:
C:\Java\Thread>java CountsTest
Runnable Class のインスタンス化終了
Thread へ受け渡し終了
A: 0
Thread の start() 終了
A: 1
B: 5
B: 4
A: 2
適時自動的に run() が実行される
B: 3
B: 2
A: 3
A: 4
A: 5
B: 1
B: 0
このように表示された場合は、標準出力への出力に時間が掛かりCPUが遊んでいるか、そもそもCPUが複数存在して複数の処理を同時に実行できるのでしょう。いずれにせよ、複数のスレッドがCPUを取り合っているので、あるスレッドでCPUが遊べば、別のスレッドがそこに割り込んで処理が平行して進みます。
このような単純なアプリケーションの場合は、実行時にCPUの遊び時間は殆ど無く、マルチスレッドによる恩恵は殆どありませんが、ファイル/DB/リモートホストなど JavaVM の外部に対する入出力やネィティブシステムのコールが必要な場合には、ボトルネックが発生し、マルチスレッドは劇的な効果を発揮します。殆ど全てのアプリケーション/アプレット/サーブレットはマルチスレッド・プログラムだと言えます。