Почему EDT не отключается, когда все живые потоки являются демонами?

Следующий код перемещает карточку по всему экрану. Когда я закрываю главное окно, я ожидаю, что поток отправки событий также закроется, но это не так. Любые идеи о том, почему поток 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);
  }
}

person Gili    schedule 09.02.2010    source источник


Ответы (4)


Поведение по умолчанию, когда вы закрываете JFrame, заключается в том, чтобы просто скрыть его, а не закрывать приложение. Вам нужно позвонить:

frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

Другими словами: это не имеет ничего общего с ScheduledExecutorService; Это связано с тем, что поток Event Dispatch не является потоком демона.

ДОПОЛНИТЕЛЬНО

Вместо того, чтобы использовать ScheduledExecutorService, который, в свою очередь, вызывает SwingUtilities.invoke..., вам следует рассмотреть возможность использования javax.swing.Timer, который будет периодически запускать ActionEvents непосредственно в потоке отправки событий, что сделает ваш код более простым/компактным и устранит необходимость в дополнительных нить.

Кроме того, вы воссоздаете ImageIcon в каждом кадре анимации, что будет очень неэффективно, особенно в тесном цикле анимации. Гораздо лучше создать его один раз при запуске приложения.

person Adamski    schedule 09.02.2010
comment
Я ценю советы по улучшению кода, но я считаю, что ваш ответ неверен. В моем коде уже используется frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE). Почему рама препятствует выключению EDT? - person Gili; 10.02.2010
comment
DISPOSE_ON_CLOSE не приведет к закрытию приложения, только EXIT_ON_CLOSE сделает это. Вы проверили описание Javadoc того, что делают эти константы? После запуска EDT (т. е. с любым приложением Swing) вы не можете просто вернуться из main(...) — поток EDT, не являющийся демоном, продолжит работу и сохранит ваше приложение. в живых; т. е. это не JFrame, в котором хранится приложение. в живых. - person Adamski; 10.02.2010
comment
Адамски, я полагаю, что вы ошибаетесь насчет DISPOSE_ON_CLOSE. Пожалуйста, прочитайте ответ, который я только что опубликовал. - person Gili; 11.02.2010

Ваша фабрика ниток правильная. Если вы установите EXIT_ON_CLOSE на фрейме, он выйдет.

Однако рассмотрите возможность использования библиотеки, такой как Trident.

person finnw    schedule 09.02.2010

Я наткнулся на ответ в этом замечательном сообщении в блоге: http://www.pushing-pixels.org/?p=369

В текущей реализации AWT завершает все свои вспомогательные потоки, позволяя приложению корректно завершить работу, когда выполняются следующие три условия:

  • Отображаемых компонентов AWT или Swing нет.
  • В очереди собственных событий нет собственных событий.
  • В java EventQueues нет событий AWT.

[...]

В текущей реализации этот тайм-аут составляет 1000 мс (или одну секунду). Фактически это означает, что AWT не выключается сразу после удаления последнего окна в вашем приложении и обработки всех ожидающих событий. Вместо этого он просыпается каждую секунду, проверяет любые ожидающие или обработанные события во время сна и продолжает спать, если такие события были.

Далее автор говорит, что его код отправляет событие в EDT каждые 100 мс, несмотря на то, что связанное с ним окно больше не отображается. Именно так и происходит в моем случае! ScheduledExecutorService отправляет события в EDT, что, в свою очередь, предотвращает закрытие AWT, что, в свою очередь, означает, что ScheduledExecutorService будет продолжать публиковать новые события.

Кроме того, я удивлен количеством людей, которые рекомендуют использовать JFrame.EXIT_ON_CLOSE. Наверное, каждому свое, но я рекомендую вам прочитать http://findbugs.sourceforge.net/bugDescriptions.html#DM_EXIT

person Gili    schedule 11.02.2010

Я думаю, что вместо того, чтобы использовать потоки демона в вашем 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 больше не передается newScheduledThreadPool
  3. Я удалил использование invokeLater для вызова main.next(), потому что единственный вызов Swing, сделанный там, - это repaint(), который является одним из немногих методов Swing, которые являются потокобезопасными.

Обратите внимание, что я не пробовал приведенный выше код, он должен скомпилироваться, и я думаю, что он также должен решить вашу проблему. Попробуйте и дайте нам знать!

person jfpoilpret    schedule 11.02.2010
comment
@jfpoilpret, вы дали хороший ответ, но мой первоначальный вопрос ввел в заблуждение. Меня больше интересовало, почему EDT не отключается, чем то, как это исправить. Я проголосовал за ваш ответ. Извините за путаницу. - person Gili; 11.02.2010
comment
На самом деле я не думаю, что проблема связана с тем, что EDT не отключается, но я считаю, что ScheduledExecutorService может иметь свой собственный рабочий поток (потоки), не созданный ThreadFactory.newThread. - person jfpoilpret; 12.02.2010
comment
Также имейте в виду, что правило, указанное в другом ответе, не работает, когда приложение запускается из Java WebStart (для этого сообщалось об ошибке) и требует явного System.exit(n). Это может быть причиной того, что некоторые люди советуют использовать JFrame.EXIT_ON_CLOSE. - person jfpoilpret; 12.02.2010
comment
Если вы хотите знать, в исходном примере, почему приложение не завершается после закрытия окна, вам следует посмотреть, какие потоки все еще выполняются в это время. Вы можете взглянуть на crazysquirrel.com/computing/java/ basics/java-thread-dump.jspx, чтобы узнать, как это сделать. Вам также придется изменить свой ThreadFactory, чтобы дать явные имена вашим собственным потокам. - person jfpoilpret; 12.02.2010