AWT 线程问题

听众和线程

除非另有说明,否则所有 AWT 监听都会在事件分派线程上得到通知。在调度期间从任何线程删除/添加监听是安全的,但更改只会影响后续通知。
例如,如果从另一个关键监听器添加了一个关键监听器,则新添加的监听器只会在后续的关键事件上得到通知。

自动关机

根据Java 虚拟机规范在第 2.17.9 和 2.19 节中,Java 虚拟机 (JVM) 最初以单个非守护线程启动,该线程通常调用某个类的 main 方法。当发生以下两种情况之一时,虚拟机将终止其所有活动并退出:
  • 所有不是守护线程的线程都会终止。
  • 某个线程调用类Runtime或类Systemexit方法,安全管理器允许退出操作。

这意味着如果应用程序本身不启动任何线程,JVM 将在 main 终止后立即退出。然而,对于创建并显示 java.awt.Frame 的简单应用程序,情况并非如此:

    public static void main(String[] args) {
      Frame frame = new Frame();
      frame.setVisible(true);
     }
原因是 AWT 封装了异步事件分派机制来处理 AWT 或 Swing 组件可以触发的事件。该机器的确切行为取决于实现。特别是,它可以出于内部目的启动非守护程序辅助线程。事实上,这些是阻止上述示例退出的线程。对该机器的行为施加的唯一限制如下:
  • EventQueue.isDispatchThread 返回 true 当且仅当调用线程是由机器启动的事件调度线程;
  • AWTEvents 实际上被排队到一个特定的 EventQueue(请注意,可以合并发布到 EventQueue 的事件):
    • 按顺序。
      也就是说,不允许同时调度来自该队列的多个事件。
    • 与他们入队的顺序相同。
      也就是说,如果 AWTEvent A 在 AWTEvent B 之前入队到 EventQueue,那么事件 B 将不会在事件 A 之前被分派。
  • 当应用程序中至少有一个可显示的 AWT 或 Swing 组件时,至少有一个活动的非守护线程(参见 Component.isDisplayable )。
第三个限制的含义如下:
  • 如果某个线程调用类Runtime或类Systemexit方法,无论是否存在可显示组件,JVM 都会退出;
  • 即使应用程序终止了它启动的所有非守护线程,只要至少有一个可显示组件,JVM 也不会退出。
一旦所有组件都变得不可显示,非守护程序辅助线程是否以及何时终止取决于实现。下面给出具体实现的细节。

依赖于实现的行为。

当以下三个条件为真时,AWT 将终止其所有辅助线程,从而允许应用程序干净地退出:
  • 没有可显示的 AWT 或 Swing 组件。
  • 本机事件队列中没有本机事件。
  • java EventQueues 中没有 AWT 事件。
因此,希望在不调用 System.exit 的情况下干净退出的独立 AWT 应用程序必须:
  • 确保在应用程序完成时所有 AWT 或 Swing 组件都不可显示。这可以通过在所有顶级 Windows 上调用 Window.dispose 来完成。参见 Frame.getFrames
  • 确保应用程序使用任何 AWT 或 Swing 组件注册的 AWT 事件监听器的任何方法都不会陷入无限循环或无限期挂起。例如,由某个 AWT 事件触发的 AWT 监听器方法可以将相同类型的新 AWT 事件发布到 EventQueue。争论的焦点是 AWT 事件监听器的方法通常在辅助线程上执行。
请注意,虽然遵循这些建议的应用程序在正常情况下会干净地退出,但不能保证它在所有情况下都会干净地退出。两个示例:
  • 其他包可以为内部需要创建可显示的组件,并且永远不会使它们不可显示。
  • 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 虚拟机规范保证在该线程终止之前 JVM 不会退出。