マルチスレッドの問題点

 マルチスレッドとは、一つのプログラムの中である処理をしながらその「間」 に別の処理(スレッド)を割り込ませる事で、まるでそのプログラムが複数の処理を同時 (並列)に行っているような効果を得られる機能です。これは非常に便利、 というより必須の機能なのですが、 注意しないととんでもない動作をする事があります。
 まず、この「ブロック崩し」 を実行してみてください。難易度はvery hard にすると良いでしょう。 どうですか? ボールが通った後に白いボールが残ったり、 バーが突然水色になったりとおかしな現象が起きますね。
 実は、これは本体とは別のスレッドを作り、 その中で一定時間毎にボールを移動し表示するようにしている (そうしないとボールの移動や表示がめちゃくちゃになる)のが原因です。  では、なぜボールやバーの色がおかしくなったのか考えてみましょう。 ボールの移動部分は以下の通りです(g_screen はゲーム画面イメージscreen に対するGraphics オブジェクト)。

		g_screen.setColor(Color.black);
		g_screen.fillOval(oldBallX*16,oldBallY*16,16,16);				

		g_screen.setColor(Color.cyan);
		g_screen.fillOval(ballX*16,ballY*16,16,16);				

 そして、バーの移動処理部分は以下の通りです。
		g_screen.setColor(Color.black);
		g_screen.fillRect(oldBarX*16,272,32,12);

		g_screen.setColor(Color.white);
		g_screen.fillRect(barX*16,272,32,12);

 この2つとも、以前のボールやバーを黒で消した後に色を設定し直し、 新しいボールやバーを描いているので、単独で見る限りどこにも問題はありません。 しかし、このボールの処理とバーの処理が「別」のスレッドで実行されているとしたら、 どうでしょうか? 例えば、まずボールを消すためにg_screen に黒を設定したとしましょう。 ここで、スレッドの切り替えが起こりバーを描くための処理でg_screen の色が白に設定されたとします。そして、処理が終わってボールを消す部分に戻ってきた時、 g_screen にはどの色が設定されているでしょうか? 黒を設定していたのに、 別のスレッドで白に変えられましたから当然ですね。つまり、 この状態でボールを描くと、その前の文では黒に設定したはずなのに、 実際には白く表示されてしまうのです。
 この問題を解決する最も簡単な方法は、 色の設定と描き変え中処理は他のスレッドと排他的に実行し、g_screen を同時に複数のスレッドで使用しない事でしょう。これなら、 設定した色を他のスレッドで換えられる事もないので安心して描き換え処理が行えます。 そして、そのためにある処理を他のスレッドと排他的に実行するのがsynchronized 文や修飾子です。  例えば、バーの移動処理は以下のようにすれば、 この処理の間は他のスレッドがg_screen を使用できなくなります。
		synchronized(this) { /*他のスレッドとは排他的実行*/

			g_screen.setColor(Color.black);
			g_screen.fillRect(oldBarX*16,272,32,12);

			g_screen.setColor(Color.white);
			g_screen.fillRect(barX*16,272,32,12);

		}

 これと同じ問題は、変数でも起こり得ます。さらにこの種のバグは、 プログラム中のスレッドをしっかりと把握していないとなかなか原因がつかめないので、 マルチスレッドのプログラムを作る時には気をつけた方が良いでしょう。なお、 synchronized は、あまり時間のかかる処理に対しては指定しない方が良いです。 時間のかかる処理をsynchronized で実行すると効率(全体の実行速度)が低下する可能性が高くなります。

synchronized 指定なしのアプレット実行
synchronized 指定なしのプログラムソース表示

synchronized 指定のアプレット実行
synchronized 指定のプログラムソース表示

戻る