Javaのプログラムではスレッドのコントロールは
Threadクラス
を通じて行われます。
ただし Threadクラスは多くのクラスの内部で既に呼び出されていることが多いので、
JDKのクラスライブラリを利用する場合には、
プログラムの表面に Threadが現れないこともあります。
まず次のような単純な例を見てみましょう。
/** メッセージ表示する Welcome クラス */
public class Welcome {
/** 起動時に呼び出されるメソッド */
public static void main( String argv[] ) {
System.out.println("Welcome to Wakkanai!");
}
}
この処理は単独のスレッドしかありません。したがって main()メソッドの
処理が終わればプログラムも終了します。
Cなどの場合と違いは見られません。
しかし、次の例は違います。
import java.awt.*;
/** フレームを表示する FrameTest クラス */
public class FrameTest {
public static void main( String argv[] ) {
Frame frame = new Frame("Frame Test"); // フレームの生成
frame.setSize( 400, 300 ); // フレームのサイズ指定
frame.setVisible( true ); // フレームの表示
System.out.println("main ended."); // すぐ実行される
}
}
上のプログラムは前回紹介したウィンドウを画面に表示させるプログラムです。
このプログラムの main()メソッドは最後まで実行されます。
(最後のメッセージはちゃんと表示されます。)
ところが、main()メソッドが終了しても、プログラムは終了しません。
Cなどの場合では、main()メソッドが終了したらプログラムは終了し、
せっかく表示したフレームも画面から消えてしまいます。
あるいは、
それを防ぐためには明示的にイベント処理のループを記述する必要があります。
ところが Javaの場合には、
特別な用意をしなくてもフレームの表示は消えたりはしません。
Frameのオブジェクトの管理は独立したスレッドによって処理されているからです。
上のようなウィンドウの表示、あるいは BGMの演奏など多くのケースでは、
マルチスレッドのことをあまり意識せず自然な処理にまかせてることで十分です。
ただし、時にはマルチスレッドであることを理解していないと正しく
プログラムの処理を把握できなくなる場合があります。
また、プログラムの処理のスタイル自体をマルチスレッドに合わせて記述する
必要が生じることもあります。
どのオブジェクトが新たにスレッドの処理を起動しいるかは、
プログラム内で使用されているクラスのすべてのソースファイルにさかのぼって
調べるか、あるいは VM に問い合わせるしかありません。
ここでは VM内のスレッドの状態を調べる方法を説明しておきましょう。
まず、VM内のスレッドは「スレッドグループ」という概念によって分類されて
管理されています。
このグループを取り扱うためのクラスが java.lang.ThreadGroup です。
スレッドグループはファイルシステムやJavaのパッケージと同じように
階層構造で管理されています。
ルートのスレッドグループから始めて、グループの内部にグループが存在できます。
それぞれのスレッドは同一のグループ内(下位のグループを含む)のスレッドの
情報を知ることはできますが、上位のグループをたどる必要があるグループの
スレッドの情報は手に入れることができません。
同じグループ内のアクティブなスレッドの個数は Thread クラスの
activeCount()メソッドで知ることができます。
上のサンプルに追加してみましょう。
import java.awt.*;
/** フレーム表示に伴うスレッドの個数を調べる FrameTest2 クラス */
public class FrameTest2 {
public static void main( String argv[] ) {
Frame frame = new Frame("Frame Test"); // フレームの生成
frame.setSize( 400, 300 ); // フレームのサイズ指定
frame.setVisible( true ); // フレームの表示
int num = Thread.activeCount(); // スレッドの個数を調べる
System.out.println("Thread number is " + num );
}
}
activeCount()は staticなメソッドです。
現在 CPUが実行中のスレッド
(この場合は main()の処理を元々担当しているデフォルトのスレッド)
と同じグループのスレッド数が表示されます。
Frameのオブジェクトが存在しなければ 1 のはずです。
Frameの処理を適宜コメントアウトしてみて、どの段階であらたなスレッドが
生成されるのか調べてみてください。)
マルチスレッドのプログラミングで注意が必要となるのは、
主に次の2つのケースでしょう。
/** 2つのスレッドが同期を取って交互に処理を行うクラス */
public class ThreadTest {
public T1 t1 = new T1(); // スレッドその1
public T2 t2 = new T2(); // スレッドその2
public static void main( String argv[] ) {
ThreadTest tt = new ThreadTest();
tt.t2.start();
tt.t1.start();
}
/** スレッドを拡張した innerクラス(その1) */
class T1 extends Thread {
public void run() {
while( true ) {
for( int i=0; i<4; i++ )
System.out.println( "I am T1" );
t2.resume();
suspend();
}
}
}
/** スレッドを拡張した innerクラス(その2) */
class T2 extends Thread {
public void run() {
while( true ) {
suspend();
for( int i=0; i<4; i++ )
System.out.println( "I am T2" );
t1.resume();
}
}
}
}
もう一つのケースである、複数のスレッドによるデータへの同時のアクセスという
問題です。これは同期を取る場合とは逆で、予想外のタイミングでデータへの
同時のアクセスが発生して混乱を生じる可能性があるということです。
したがって、それを防ぐためにあらかじめデータへの保護(ロック)
の措置を用意する必要があります。
この目的のために、Javaには synchronized という修飾子が用意されています。
この修飾子はメソッドもしくはブロックに対して用いることができます。
メソッドの場合には、そのメソッドのオブジェクト自身がロックの対象となります。
ブロックの処理の場合には、synchronized の後にロックの対象となるオブジェクト
を指定することができます。
以下の例は、データへのロックによって値がおかしくならないようにコントロール
した例です。synchronized の指定をはずすとどうなるかも試してみてください。
/** マルチスレッドによるデータのアクセスを実験する MultiAccessクラス */
public class MultiAccess {
/** 変更する文字列 */
protected StringBuffer message= new StringBuffer( "Wakkanai" );
/** 最初に呼び出されるメソッド */
public static void main( String argv[] ) {
MultiAccess ma = new MultiAccess();
MessageEditor editor = ma.new MessageEditor();
MessageReader reader = ma.new MessageReader();
editor.start();
reader.start();
}
/** スレッドの処理を実行する MessageEditorクラス */
class MessageEditor extends Thread {
/** スレッドによって実行される処理(文字列を変更して、元に戻す) */
public void run(){
while( isAlive() ) {
synchronized( message ){ // messageオブジェクトにロック
message.setLength( message.length() - 1 ); //末尾の文字を削除
try{
Thread.sleep( 500 ); // 0.5秒休む
} catch( Exception e ){}
message.append( 'i' ); // 末尾に文字を追加
}
try{
Thread.sleep( 500 ); // 0.5秒休む
} catch( Exception e ){}
}
}
} // MessageEditorクラスの定義の終わり
/** スレッドの処理を実行する MessageReaderクラス */
class MessageReader extends Thread {
/** スレッドによって実行される処理 */
public void run(){
while( isAlive() ) {
try{
Thread.sleep( 800 ); // 0.8秒休む
} catch( Exception e ){}
System.out.println( message.toString() );
}
}
} // MessageReaderクラスの定義の終わり
} // MultiAccessクラスの定義の終わり