WisdomSoft - for your serial experiences.

9.1 並行処理

プログラムの実行単位であるスレッドについて解説します。複数のスレッドを同時に実行するマルチスレッドによって、異なるコードを並行に実行できます。

9.1.1 スレッドを生成する

Java 言語はマルチスレッドを考慮したプログラミング言語です。マルチスレッドとは、プログラムを同時並行的に実行するための仕組みで、単一のアプリケーションが複数のコードを同時に実行することができます。マルチスレッドの原理については本書の役目を越えてしまうため、コンピュータ・システム全般の教本を参照してください。

システムがマルチスレッドを実現できるかどうかという問題についてはコンピュータに依存しますが、すべての Java 仮想マシンは同時に複数のスレッドを実行することができると定められています。スレッドとはアプリケーション内のコードを実行する処理単位のことで、main() メソッドを実行するスレッドをメインとしています。

通常は、main() メソッドを実行する単一のスレッドだけでプログラムが実行されますが、必要に応じて新しいスレッドを生成することができます。このように、複数のスレッドを保有することによって、プログラムのコードを同時並行的に実行することができるようになるのです。スレッドを生成するには Thread クラスを用います。Thread クラスは標準クラスライブラリで定義されており、Thread クラス以外の方法でスレッドを生成することはできません。

プログラムの並行処理を実践で正しく利用するにはプロフェッショナルでも悩まされるほどの高度な設計が必要ですが、スレッドで並行処理すること自体は単純です。何らかのコードを他のスレッドで実行したい場合、Thread クラスが宣言している run() メソッドをオーバーライドすればよいだけです。あとは、スレッドを走らせれば run() メソッドが呼び出されます。Thread メソッドの主なメンバは表1を見てください。

表1 Thread クラス
コンストラクタ 解説
Thread() 新しい Thread オブジェクトを割り当てます。
Thread(Runnable target) 新しい Thread オブジェクトを割り当てます。
Thread(Runnable target, String name) 新しい Thread オブジェクトを割り当てます。
Thread(String name) 新しい Thread オブジェクトを割り当てます。
Thread(ThreadGroup group, Runnable target) 新しい Thread オブジェクトを割り当てます。
Thread(ThreadGroup group, Runnable target, String name) その実行オブジェクトとして target 、名前として指定された name を持つ、group によって参照されるスレッドグループに属するような、新しい Thread オブジェクトを割り当てます。
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 新しい Thread オブジェクトを割り当て、実行オブジェクトとして target を保持し、指定された name を名前として保持するようにします。
Thread(ThreadGroup group, String name) 新しい Thread オブジェクトを割り当てます。
メソッド
static int activeCount() 現行スレッドのスレッドグループ内のアクティブなスレッド数を返します。
void checkAccess() 現在実行中のスレッドが、このスレッドを変更するためのアクセス権を持っているかどうかを判定します。
static Thread currentThread() 現在実行中のスレッドオブジェクトの参照を返します。
void destroy() クリーンアップを行わないでこのスレッドを破棄します。
static void dumpStack() 現在のスレッドのスタックトレースを出力します。
static int enumerate(Thread[] tarray) 現行スレッドのスレッドグループおよびその下位グループ内のすべてのアクティブなスレッドを、指定された配列にコピーします。
ClassLoader getContextClassLoader() この Thread のコンテキスト ClassLoader を返します。
String getName() このスレッドの名前を返します。
int getPriority() このスレッドの優先順位を返します。
ThreadGroup getThreadGroup() このスレッドが所属するスレッドグループを返します。
static boolean holdsLock(Object obj) 現行スレッドが指定されたオブジェクトに対するモニターロックを保持する場合にのみ、true を返します。
void interrupt() このスレッドに割り込みます。
static boolean interrupted() 現在のスレッドが割り込まれているどうかを調べします。
boolean isAlive() このスレッドが生存しているかどうかを判定します。
boolean isDaemon() このスレッドがデーモンスレッドであるかどうかを判定します。
boolean isInterrupted() このスレッドが割り込まれているどうかを調べます。
void join() このスレッドが終了するのを待機します。
void join(long millis) このスレッドが終了するのを、最高で millis ミリ秒待機します。
void join(long millis, int nanos) 最高で millis ミリ秒に nanos ナノ秒を加算した間、このスレッドが終了するのを待機します。
void run() このスレッドが別個の Runnable 実行オブジェクトを使用して作成された場合、その Runnable オブジェクトの run メソッドが呼び出されます。
void setContextClassLoader(ClassLoader cl) この Thread のコンテキスト ClassLoader を設定します。
void setDaemon(boolean on) このスレッドを、デーモンスレッドまたはユーザスレッドとしてマークします。
void setName(String name) このスレッドの名前を引数 name に等しくなるように変更します。
void setPriority(int newPriority) このスレッドの優先順位を変更します。
static void sleep(long millis) 現在実行中のスレッドを、指定されたミリ秒数の間、一時的に実行を停止させます。
static void sleep(long millis, int nanos) 現在実行中のスレッドを、指定されたミリ秒数に指定されたナノ秒数を加算した間、実行停止させます。
void start() このスレッドの実行を開始します。
String toString() スレッドの名前、優先順位、スレッドグループを含むこのスレッドの文字列表現を返します。
static void yield() 現在実行中のスレッドオブジェクトを一時的に休止させ、ほかのスレッドが実行できるようにします。

メインスレッドに対して並列的に実行したいコードは、この Thread クラスを継承して記述します。Thread クラスを継承するクラスは run() メソッドをオーバーライドし、メソッドの本体に必要な処理を記述します。スレッドが開始すればシステムによって run() メソッドが呼び出されます。ただし、Thread クラスのインスタンスを生成するだけではスレッドは実行されません。新しいスレッドは start() メソッドを呼び出すことによって走り出します。

複数のスレッドが物理的にどのように処理されるかという問題は、コンピュータの構造に依存します。CPU が単一ならば、CPU の空き時間を利用して、CPU の使用権をスレッドが交換し合いながら並列に処理しているかのように見せかけるでしょう。マルチプロセッサやスレッド機能が搭載された CPU などであれば、物理的にも並列に計算が行われる可能性があるでしょう。マルチスレッド対応のアプリケーション開発者はこうしたスレッドやマルチタスクの原理にある程度精通している必要があります。スレッドの原理についてはコンピュータ・システムの専門書を参照してください。

コード1
class Test extends Thread {
	public static void main(String args[]) {
		new Test().start();
		for(int i = 0 ; i < 5 ; i++)
			System.out.println("Blue Blue Glass Moon, ");
	}
	public void run() {
		for(int i = 0 ; i < 5 ; i++)
			System.out.println("Under The Crimson Air.");
	}
}
実行結果
>java Test
Blue Blue Glass Moon,
Blue Blue Glass Moon,
Blue Blue Glass Moon,
Under The Crimson Air.
Under The Crimson Air.
Under The Crimson Air.
Under The Crimson Air.
Under The Crimson Air.
Blue Blue Glass Moon,
Blue Blue Glass Moon,

コード1は、Java でマルチスレッドを実現するためのプログラムです。Test クラスは新しいスレッドを生成する Thread クラスを継承しています。まず main() メソッドがメイン・スレッドで実行されます。このとき Test クラスのインスタンスを生成し Thread クラスの start() メソッドを呼び出します。Test クラスは Thread クラスのサブクラスなので、start() メソッドを呼び出すことによって新しいスレッドを走らせることができるのです。

start() メソッドが呼び出されると、その時点で新しいスレッドが起動します。スレッドは Thread クラスの run() メソッドを呼び出します。新しいスレッドは、この run() メソッドが終了するまで有効となります。run() メソッドから制御を返した時点で、そのスレッドは処理を終了します。

プログラムは、main() メソッド内でメイン・スレッドを使って繰り返し処理をする一方、run() メソッドで新しく生成したサブ・スレッドを使って繰り返し処理を実行します。これらの処理は同時並行的に行われるため、それぞれの処理は互いに関与しません。表示される文字列の順序はそのときのCPUの状況によって変化するため、実行結果は常に不定となります。ある時点ではメイン・スレッドが処理されていますが、突然サブ・スレッドに制御が移行します。スレッドの切り替えを何ミリ秒単位で行うかはシステムが判断します。

マルチスレッドを採用するプログラムのデバッグを行うときは、コードを実行している現在のスレッドが、どのスレッドなのか知ることが重要になります。Thread オブジェクトには、スレッドの名前を設定することができるので、これを利用すれば複数のスレッドを実行中に識別することができます。

スレッドの名前を新しく設定するには Thread クラスの setName() メソッドを用います。設定したスレッドの名前は getName() メソッドから取得することができます。異なるスレッドが同じ名前を保有することもできますが、好ましくはありません。デフォルトでは、新しい名前が設定されています。currentThread() メソッドを用いれば現在のコードを実行しているスレッドの Thread オブジェクトを得られるので、このスレッドから getName() メソッドで取得すれば、現在実行しているスレッドが何番目のスレッドなのかを調べることができます。

9.1.2 スレッドの待機

Thread クラスの sleep() メソッドを用いれば、現在実行中のスレッドを指定時間停止させることができます。スレッドが停止している間は他のプロセスやスレッドに制御が移されます。何らかの理由で処理を停止したい場合は、Thread.sleep() を利用しましょう。今後、スレッドの動きをより正確に追うために sleep() メソッドを使います。

sleep() メソッドで 1 秒間プログラムが待機するように指定したとしても、必ず時間が守られる保障はありません。指定時間が経過しても、システムや CPU の利用状況によっては待ち状態が続きます。そのため、時間を計測する手段として用いることはできません。

また、メイン・スレッドを長時間停止するようなことも避けなければなりません。実践のプログラムでメイン・スレッドを停止してしまうと、プログラムが操作不能となってしまう可能性があります。

コード2
class Test extends Thread {
	public static void main(String args[]) {
		new Test("Blue Blue Glass Moon," , 500).start();
		new Test("\tUnder The Crimson Air." , 800).start();
	}

	private String str;
	private long interval = 10;
	public Test(String str , long interval) {
		this.str = str;
		this.interval = interval;
	}
	public void run() {
		for(int i = 0 ; i < 5 ; i++) {
			System.out.println(str);
			try { Thread.sleep(interval); }
			catch(InterruptedException err) {
				System.out.println(err);
			}
		}
	}
}
実行結果
>java Test
Blue Blue Glass Moon,
        Under The Crimson Air.
Blue Blue Glass Moon,
        Under The Crimson Air.
Blue Blue Glass Moon,
Blue Blue Glass Moon,
        Under The Crimson Air.
Blue Blue Glass Moon,
        Under The Crimson Air.
        Under The Crimson Air.

コード2は、Thread.sleep() メソッドを用いて意図的にスレッドを停止させています。新しく生成したスレッド(Test クラスのインスタンス)を走らせると、コンストラクタで指定した文字を、指定した時間間隔で表示します。sleep() メソッドの引数はミリ秒単位の時間です。1000 を指定すれば、最低でも 1 秒間はスレッドが停止します。

プログラムを実行すると、一気に文字が表示されるのではなく、時間間隔ごとに表示されることが確認できます。sleep() メソッドは InterruptedException をスローする可能性があるため、この例外を取り扱う必要があります。この例外は、スレッドを停止している間にThread.interrupt() メソッドが呼び出された場合に発生します。