AWTのスレッドの問題

リスナーとスレッド

特に断りのないかぎり、すべてのAWTリスナーはイベント・ディスパッチ・スレッド上で通知されます。 ディスパッチ中に任意のスレッドからリスナーを追加/削除しても安全ですが、変更はその後の通知のみに影響します。
たとえば、キー・リスナーが別のキー・リスナーから追加された場合、新しく追加されたリスナーにはその後のキー・イベントのみが通知されます。

自動シャットダウン

「Java Virtual Machine仕様」の2.17.9および2.19の項によると、Java仮想マシン(JVM)は最初は単一のデーモン以外のスレッドで起動します。このスレッドは通常、一部のクラスのmainメソッドを呼び出します。 仮想マシンは、次の2つのうちのいずれかが発生した場合にすべてのアクティビティを終了し、仮想マシン自身を終了します。
  • 非デーモン・スレッドがすべて終了した場合。
  • あるスレッドが、クラスRuntimeまたはクラスSystemexitメソッドを呼び出し、セキュリティ・マネージャによってexit動作が許可された場合。

これは、アプリケーション自身がスレッドを開始しない場合、JVMはmainが終了するとただちに終了することを意味します。 ただし、java.awt.Frameを作成して表示する単純なアプリケーションの場合にはこれは当てはまりません。

        public static void main(String[] args) {
            Frame frame = new Frame();
            frame.setVisible(true);
         }
その理由は、AWTが、AWTまたはSwingコンポーネントがトリガーできるイベントを処理するために、非同期イベント・ディスパッチ機構をカプセル化するからです。 この機構の正確な動作は実装によって異なります。 具体的には、内部的な目的で非デーモンのヘルパー・スレッドを開始できます。 実際に、上記の例ではこれらのスレッドが終了を妨げています。 この機構の動作に適用される制限は、次のもののみです。
  • EventQueue.isDispatchThreadは、呼出し元スレッドがこの機構によって開始されたイベント・ディスパッチ・スレッドである場合にかぎりtrueを返します。
  • 特定のEventQueue (EventQueueに送られたイベントは合体できる)に実際に入れられたAWTEventsは次のようにディスパッチされます。
    • 順次
      これが指定されている場合、このキューの複数イベントの同時ディスパッチは許可されません。
    • キューに入れられた順序
      AWTEvent AがAWTEvent Bよりも前にEventQueueに入れられた場合、イベントBをイベントAよりも前にディスパッチすることはできません。
  • アプリケーション内に少なくとも1つの表示可能なAWTまたはSwingコンポーネントがある間は、少なくとも1つの非デーモン・スレッドが生存しています(Component.isDisplayable参照)。
3番目の制限の意味は次のとおりです。
  • 任意のスレッドがクラスRuntimeまたはクラスSystemexitメソッドを起動した場合、JVMは表示可能なコンポーネントが存在するかどうかにかかわらず終了します。
  • アプリケーションが自身で開始した非デーモン・スレッドをすべて終了した場合でも、少なくとも1つの表示可能なコンポーネントが存在するかぎり、JVMは終了しません。
すべてのコンポーネントが表示不可になった場合に、非デーモン・ヘルパー・スレッドが終了するかどうか、またいつ終了するかは実装に依存します。 実装固有の詳細については後述します。

実装依存の動作。

1.4より前では、ヘルパー・スレッドは決して終了しませんでした。

1.4から、4030718の修正の結果、この動作は変更されました。 現在の実装では、次の3つの条件が満たされた場合、AWTはそのすべてのヘルパー・スレッドを終了し、アプリケーションが正常に終了できるようにします。

  • 表示可能なAWTまたはSwingコンポーネントが存在しない。
  • ネイティブ・イベント・キューにネイティブ・イベントが存在しない。
  • javaのEventQueueにAWTイベントが存在しない。
したがって、System.exitを呼び出さずに正常に終了したいスタンドアロンAWTアプリケーションは、次を確認する必要があります。
  • アプリケーションの終了時に、すべてのAWTまたはSwingコンポーネントが表示不可になっていること。 これは、すべてのトップ・レベルのWindowsWindow.disposeを呼び出すことにより実行できます。 Frame.getFramesを参照してください。
  • アプリケーションによってAWTまたはSwingコンポーネントに登録されたAWTイベント・リスナーのメソッドが、無限ループに入ったり無期限にハングしたりしないこと。 たとえば、AWTイベントによってトリガーされたAWTリスナーのメソッドが、同じ型の新しいAWTイベントをEventQueueに送信することがあります。 問題は、AWTイベント・リスナーのメソッドは通常ヘルパー・スレッドで実行されることです。
これらの推奨事項に従うアプリケーションは、通常の条件下では正常に終了しますが、あらゆる場合に正常に終了することが保証されているわけではありません。 例を2つ次に挙げます。
  • ほかのパッケージが、内部的な必要性のために表示可能なコンポーネントを作成して表示不可にしない場合。 45150584671025、および4465537を参照してください。
  • Microsoft WindowsとX11はいずれも、アプリケーションがネイティブ・イベントをほかのアプリケーションに属するウィンドウに送信することを許可します。 この機能を使用すると、使用可能なすべてのウィンドウにイベントを送信し続けてAWTアプリケーションが正常に終了することを妨げるような、悪意のあるプログラムを作成できます。
一方、アプリケーションがすべてのコンポーネントを表示不可にしたあとでもJVMが実行を継続することを必要とする場合は、永遠にブロックする非デーモン・スレッドを開始する必要があります。
        <...>
        Runnable r = new Runnable() {
            public void run() {
                Object o = new Object();
                try {
                    synchronized (o) {
                        o.wait();
                    }
                } catch (InterruptedException ie) {
                }
            }
        };
        Thread t = new Thread(r);
        t.setDaemon(false);
        t.start();
        <...>
「Java Virtual Machine仕様」は、このスレッドが終了するまでJVMが終了しないことを保証します。