当所有活动线程都是守护进程时,为什么EDT不会关闭?

时间:2010-02-09 17:32:33

标签: java swing executorservice

以下代码在屏幕上滑动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);
  }
}

4 个答案:

答案 0 :(得分:3)

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

frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

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

其他

您应该考虑使用ScheduledExecutorService而不是使用SwingUtilities.invoke...来调用javax.swing.Timer,而ActionEvent会直接在事件调度线程上定期激活ImageIcon ,从而使您的代码更简单/更紧凑,无需额外的线程。

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

我在这篇优秀的博文中遇到了答案: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

答案 3 :(得分:1)

我认为,不是在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();
      }
  });
}

请注意我对您的代码段所做的更改:

  1. timer现已宣布为final(需要 因为它是由内部引用的 匿名课程)
  2. ThreadFactory
  3. 不再传递newScheduledThreadPool
  4. 我删除了 使用invokeLater进行通话 main.next()因为唯一的Swing 打电话有repaint() 是少数Swing方法之一 是线程安全的。
  5. 请注意,我没有尝试过上面的代码,它应该编译,我认为它也应该解决你的问题。试一试,告诉我们!