【问题标题】:Why doesn't the EDT shut down when all live threads are daemon?当所有活动线程都是守护进程时,为什么 EDT 不关闭?
【发布时间】:2010-02-09 17:32:32
【问题描述】:

以下代码在屏幕上滑动card。当我关闭主窗口时,我希望事件调度线程也会关闭,但事实并非如此。关于 ScheduledExecutorService 线程为何阻止 EDT 关闭的任何想法?

import java.awt.Graphics;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main extends JPanel
{
  private float x = 1;

  public void next()
  {
    x *= 1.1;
    System.out.println(x);
    repaint();
  }

  @Override
  protected void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    URL url = getClass().getResource("/209px-Queen_of_diamonds_en.svg.png");
    g.drawImage(new ImageIcon(url).getImage(), (int) x, 50, null);
  }

  public static void main(String[] args)
  {
    JFrame frame = new JFrame();
    final Main main = new Main();
    frame.getContentPane().add(main);
    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setVisible(true);

    ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, new ThreadFactory()
    {
      public Thread newThread(Runnable r)
      {
        Thread result = new Thread(r);
        result.setDaemon(true);
        return result;
      }
    });
    timer.scheduleAtFixedRate(new Runnable()
    {
      public void run()
      {
        SwingUtilities.invokeLater(new Runnable()
        {
          public void run()
          {
            main.next();
          }
        });
      }
    }, 100, 100, TimeUnit.MILLISECONDS);
  }
}

【问题讨论】:

    标签: java swing executorservice


    【解决方案1】:

    关闭JFrame 时的默认行为只是隐藏它,而不是导致应用程序退出。您需要致电:

    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    

    换句话说:这与ScheduledExecutorService无关;这与 Event Dispatch 线程不是守护线程有关。

    附加

    而不是使用 ScheduledExecutorService 反过来调用 SwingUtilities.invoke... 您应该考虑使用 javax.swing.Timer,它将定期直接在事件调度线程上触发 ActionEvents,从而使您的代码更简单/更紧凑,并且不需要额外的线程。

    此外,您在每个动画帧上重新创建ImageIcon,这将非常低效,尤其是在紧凑的动画循环中。最好在应用程序启动时创建一次。

    【讨论】:

    • 我很欣赏代码改进提示,但我相信你的回答是错误的。我的代码已经使用了 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)。为什么框架会阻止 EDT 关闭?
    • DISPOSE_ON_CLOSE 不会导致应用程序退出,只有 EXIT_ON_CLOSE 会这样做 - 您是否检查过 Javadoc 描述这些常量的作用?一旦 EDT 运行(即使用任何 Swing 应用程序),您就不能简单地从 main(...) 返回 - 非守护程序 EDT 线程将继续运行并保留您的应用程序。活;即,保留应用程序的不是专门的 JFrame。活着。
    • Adamski,我相信您对 DISPOSE_ON_CLOSE 有误。请阅读我刚刚发布的答案。
    【解决方案2】:

    你的线程工厂是正确的。如果你在框架上设置EXIT_ON_CLOSE,那么它就会退出。

    但是,请考虑改用 Trident 之类的库。

    【讨论】:

      【解决方案3】:

      我在这篇出色的博文中找到了答案:http://www.pushing-pixels.org/?p=369

      在当前的实现中,当满足以下三个条件时,AWT 会终止其所有帮助线程,从而允许应用程序干净地退出:

      • 没有可显示的 AWT 或 Swing 组件。
      • 本机事件队列中没有本机事件。
      • java EventQueues 中没有 AWT 事件。

      [...]

      在当前实现中,此超时为 1000 毫秒(或一秒)。这实际上意味着 AWT 在处理应用程序中的最后一个窗口并处理所有未决事件后不会立即关闭。相反,它每秒唤醒一次,在睡眠期间检查任何未决或已处理的事件,如果有任何此类事件,则继续睡眠。

      作者接着说,尽管相关的窗口不再可见,但他的代码每 100 毫秒向 EDT 发布一个事件。这正是我的情况! ScheduledExecutorService 将事件发布到 EDT,这反过来会阻止 AWT 关闭,这反过来意味着 ScheduledExecutorService 将继续发布更多事件。

      顺便说一句,我对推荐使用JFrame.EXIT_ON_CLOSE 的人数感到惊讶。我猜每个人都有自己的想法,但我建议你阅读http://findbugs.sourceforge.net/bugDescriptions.html#DM_EXIT

      【讨论】:

        【解决方案4】:

        我认为,与其在ScheduledExecutorService 中使用守护线程,不如在用户想要退出时明确关闭它。 您可以通过向主框架添加一个 WindowListener 来做到这一点:

          public static void main(String[] args)
          {
            JFrame frame = new JFrame();
            final Main main = new Main();
            frame.getContentPane().add(main);
            frame.setSize(800, 600);
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.setVisible(true);
        
            final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
            timer.scheduleAtFixedRate(new Runnable()
            {
              public void run()
              {
                // NOTE that you don't need invokeLater here because repaint() is thread-safe
                main.next();
              }
            }, 100, 100, TimeUnit.MILLISECONDS);
          }
          // Listen to main frame closure and shut down timer
          main.addWindowListener(new WindowAdapter()
          {
              public void windowClosed(WindowEvent e)
              {
                  timer.shutdownNow();
              }
          });
        }
        

        注意我对你的 sn-p 所做的更改:

        1. timer 现在声明为 final(需要 因为它被内部引用 匿名类)
        2. 没有更多的ThreadFactory 传递给newScheduledThreadPool
        3. 我已删除 使用invokeLater 调用 main.next() 因为唯一的 Swing 在那里打的电话是repaint() 是为数不多的 Swing 方法之一 是线程安全的。

        请注意,我没有尝试过上面的代码,它应该可以编译,我认为它也应该可以解决您的问题。试试看,让我们知道!

        【讨论】:

        • @jfpoilpret,您提供了一个很好的答案,但我最初的问题具有误导性。我更感兴趣的是为什么 EDT 没有关闭,然后是如何修复它。我对你的回答投了赞成票。很抱歉造成混乱。
        • 其实我认为问题不是因为 EDT 没有关闭,但我相信 ScheduledExecutorService 可能有自己的工作线程,不是由 ThreadFactory.newThread 创建的。
        • 另请注意,当应用程序从 Java WebStart 启动时,其他答案中给出的规则不起作用(为此报告了一个错误)并且需要显式 System.exit( n)。这可能是有些人建议使用 JFrame.EXIT_ON_CLOSE 的原因。
        • 如果你想知道,在你原来的例子中,为什么应用程序在关闭窗口后没有退出,那么你应该看看当时哪些线程仍在运行。你可以看看crazysquirrel.com/computing/java/basics/java-thread-dump.jspx 看看怎么做。您还必须修改您的 ThreadFactory 以为您自己的线程提供明确的名称。
        猜你喜欢
        • 2010-11-27
        • 2022-08-08
        • 1970-01-01
        • 2017-10-28
        • 2011-10-27
        • 1970-01-01
        • 2012-03-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多