Swing とスレッド


シングルスレッド設計

一般的に Swing はシングルスレッド設計であり、他のスレッドが介入してはいけない
逆に言えば、Swing は常に一つのスレッドからのみアクセスすることができます
なぜ、Javaの得意分野である「マルチスレッド」を切り捨てるようなことをしたのでしょうか?

マルチスレッドはプログラムが複雑で上級プログラマであっても困難といわれます
スレッドセーフクラスを拡張するということは、プログラマも熟練者である必要があります
また、スレッドの状態をチェックし同期をとるという動作もオーバーヘッドにつながります
このような拡張の簡易化や動作の合理化のために Swing はシングルスレッド設計なのです

正確には、Swing コンポーネントが描画されてからが対象になります
その後は、イベントディスパッチスレッドからのみアクセス可能となります
イベントディスパッチスレッドとは、コールバックメソッドである update() や paint() メソッド
それから、イベントリスナで定義されているイベントメソッドを呼び出すためのスレッドです

しかし、当然これだけではアプリケーションの建築ができません
長期にわたることが予想される処理や、無限ループによるアニメーションを
イベントディスパッチスレッドで行えば、他のイベント処理が受けつけられなくなります
そこで javax.swing.SwingUtilities クラスを使用してこの問題を解決します
java.lang.Object
  |
  +--javax.swing.SwingUtilities
このクラスは Swing のユーティリティメソッドのコレクションです
メソッドはスタティックメソッドであり、コンストラクタはありません

public class SwingUtilities extends Object
implements SwingConstants

SwingConstants インターフェイスは、コンポーネントの配置
および方向指定を行うために使う、定数を定義しています

イベントディスパッチスレッド以外のスレッドからGUIの描画処理を行う場合
このクラスの invokeLater() メソッドを使用します

public static void invokeLater(Runnable doRun)

doRun には、走らせたいスレッドを指定します
このメソッドは、イベントディスパッチ元で doRun.run() メソッドを非同期に実行します
ただし、保留中のすべての AWT イベントが処理されたあとに発生します
その関係はキューであり、このメソッドは実行待ちAWTイベントに
オブジェクトをエンキューし、すぐに制御を戻します

ただし、Swing コンポーネントでも repaint()、revalidate()、invalidate() は
他の任意のスレッドから安全に呼び出すことができます
これらのメソッドは、invokeLater() 同様に
イベントディスパッチスレッドに対して、処理の要求を行うメソッドだからです
import javax.swing.*;
import java.awt.*;

public class Test extends JFrame implements Runnable {
	public static void main(String args[]) {
		JFrame frame = new Test();
		frame.setBounds(10 , 10 , 400 , 300);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.show();
	}

	private int color;
	public Test() { new Thread(this).start(); }
	public void run() {
		while(true) {
			color += 0x050505;
			if (color == 0xFFFFFF) color = 0;

			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					setForeground(new Color(color));
				}
			});
			Thread.sleep(100);
		}
	}
	public void paint(Graphics g) {
		g.fillRect(0 , 0 , getWidth() , getHeight());
	}
}

このプログラムは、徐々にウィンドウの作業領域を黒から白に変化させます
こうしたアニメーションは、イベントディスパッチスレッド以外で行う必要があります
しかし、他のスレッドから Swing コンポーネントのメソッドにアクセスするべきではありません
そこで、このプログラムでは invokeLater() メソッドに
Runnable インタフェースを実装する匿名クラスのオブジェクトを渡しています

invokeLater() メソッドに指定した Runnable オブジェクトの run() メソッドは
イベントディスパッチスレッドで呼び出されることを忘れないでください
このメソッドは、すぐに制御を返さなければ、アプリケーション制御不能になります

一般には、コンポーネントの設定を変更したり、取得する処理を記述した
Runable オブジェクトを invokeLater() メソッドに渡すことになるでしょう

SwingUtilities クラスは、invokeAndWait() メソッドという手段も提供しています
このメソッドは、イベントディスパッチスレッド以外から GUI を更新するもう一つの方法です

public static void invokeAndWait(Runnable doRun)
throws InterruptedException , InvocationTargetException

doRun には、実行させたいスレッドを指定します
invokeLater() メソッドはキューにオブジェクトを登録してから即座に制御を返しますが
この invokeAndWait() メソッドは doRun.run() を実行するまで制御を返しません

ただし、この invokeAndWait() メソッドは invokeLater() メソッドと異なり
イベントディスパッチスレッドから呼び出せません
なぜならば、このメソッドは保留されているイベントの終了を待ち続けるため
イベントディスパッチスレッドで呼び出すと、run() メソッドの終了を待ちます
しかし、run() を呼び出すにはイベントディスパッチスレッドが空かなければなりません
ところが、run() の終了を待っているためイベントディスパッチスレッドは動きません
このような状態になってしまいます
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Test implements ActionListener {
	public static void main(String args[]) {
		Button bt = new Button("Kitty on your lap");
		bt.addActionListener(new Test());

		JFrame frame = new JFrame("Kitty on your lap");
		frame.setBounds(10 , 10 , 400 , 300);
		frame.getContentPane().setLayout(new FlowLayout());
		frame.getContentPane().add(bt);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.show();
	}
	public void actionPerformed(ActionEvent e) {
		try {
			SwingUtilities.invokeAndWait(new Runnable() {
				public void run() {}
			});
		}
		catch(Exception err) {}
	}
}
このプログラムを実行し、ボタンを押すとエラーが発生します
これは、スレッドが実行を終えるのを待つ間に割り込みを受けたためです
invokeAndWait() を invokeLater() メソッドに変更すれば
このようなエラーがことが発生しないことを確認できます

次の章で説明する Swing コンポーネントの描画処理に、この知識は不可欠です
ただし、JApplet や JFrame は AWT の Applet や Frame を継承する
重量コンポーネントなので、純粋な Swing の軽量コンテナと区別してください



前のページへ戻る次のページへ