昔は、以下のようにmainメソッド内でSwingの初期化と表示を行っていました。
public static void main(final String[] args) {
JFrame frame = new JFrame("MyApp");
:
frame.setVisible(true);
}
ところが、これはスレッド安全性上いかんということです。
では、どうやって書けばよいかを以下に示します。
private static void createAndShowGUI() {
JFrame frame = new JFrame("MyApp");
:
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
setVisible(true)呼び出しの中でイベントディスパッチスレッドが生成され、まだsetVisibleの処理が完了するまえにイベントディスパッチスレッドがリスナーへの通知を開始することがあり得るためです。詳しくは参考の記事をご覧ください。
java.awt.ComponentのsetVisible()実装を調べる
JFrameのsetVisible()メソッドは、java.awt.Componentクラスで定義されています。
- java.awt.Component.setVisible(boolean)
- java.awt.Component.show(boolean)
- java.awt.Component.show()
と呼び出し連鎖が続きます。show()メソッドの処理概要は以下のとおり。
- peerのshow()メソッド呼び出し
- HierarchyEvent.HIERARCHY_CHANGEDイベントの発生
- カーサの更新
- ComponentEvent.COMPONENT_SHOWNイベントの発生
- 親コンポーネントのinvalidate()
ここでマルチスレッド上の問題があるとすれば、2つのイベント発生にあると考えられます。イベントは、AWTのイベントキューに追加され、イベント・ディスパッチ・スレッドによって処理されます。
したがって、setVisible()メソッドをイベント・ディスパッチ・スレッド以外から呼び出した場合、イベント発生によってイベント・ディスパッチ・スレッドが実行されることにより、競合が発生する可能性があります。
CPUが1つしかない(正確には同時に一つのスレッドしか実行できない)場合は、OSのスケジューラによって逐次的に処理が行われるため、ほとんどの場合setVisible()メソッドを実行しているスレッドが先に処理を完了してからイベント・ディスパッチ・スレッドの処理が動くことになると考えられます。
プログラムとしてスレッド安全性が損なわれていても大半の場合は問題なく動いてしまいます。
稀にタイムスライスを使い切ってsetVisible()が終了する前にイベント・ディスパッチ・スレッドにスケジューラが処理を移してしまい、問題となることが考えられます。
一方、複数CPUや、マルチコアのCPUなど同時に複数スレッドが実行できる場合は、OSのスケジューラが同時にsetVisible()メソッドのスレッドとイベント・ディスパッチ・スレッドの処理を実行させることができるので、スレッド安全性が損なわれたプログラムによるエラーが単一スレッド環境に比べて顕在化します。
昨今の計算機構成を見ると、GUIアプリケーションが動くデスクトップやノートPCにおいても、デュアルコア・プロセッサを搭載したものが増えており、来年にはクァッドコア・プロセッサも登場すると言われております。これからは、スレッド安全性をしっかり作りこんだプログラム開発が必要でしょう。
スレッド安全性にかかわるバグは、バグ報告があっても再現性が少なく、おそらく「再現待ち」というラベルを貼られてお蔵入りになるだけでしょう。現在のソフトウェア開発力ではこの種のバグを発見し修正することは非常に困難です。