为什么SwingUtilities.invokeLater()会导致JButton冻结?

时间:2015-07-22 18:56:44

标签: java multithreading swing

考虑这个基本的Swing程序,由两个按钮组成:

public class main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("hi!");
        JPanel mainPanel = new JPanel(new GridLayout());
        JButton longAction = new JButton("long action");
        longAction.addActionListener(event -> doLongAction());
        JButton testSystemOut = new JButton("test System.out");
        testSystemOut.addActionListener(event -> System.out.println("this is a test"));
        mainPanel.add(longAction);
        mainPanel.add(testSystemOut);
        jf.add(mainPanel);
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
    }

    public static void doLongAction() {
        SwingUtilities.invokeLater(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted!");
            }
            System.out.println("Finished long action");
        });

    }
}

我希望我的第二个按钮testSystemOut可用,而第一个按钮正在进行长动作(这里,我在其中放了3秒钟)。我可以通过手动将doLongAction()放入Thread并致电start()来实现。但我知道我应该使用SwingUtilities代替EventQueue,这与{{1}}完全相同。但是,如果我这样做,我的按钮会在其动作期间冻结。

为什么?

2 个答案:

答案 0 :(得分:2)

使用SwingUtilities.invokeLater,您在Swing事件线程上调用附带的代码,包括Thread.sleep(...)调用,这是您应该从不做的事情,因为它放置整个事件线程,负责绘制GUI并响应用户输入的线程,即睡眠 - 即它冻结您的应用程序。解决方案:使用Swing Timer代替,或者在后台线程中休眠。如果要调用长时间运行的代码并使用Thread.sleep(...)进行模拟,请使用SwingWorker为您完成后台工作。有关详细信息,请阅读Concurrency in Swing。请注意,没有理由让SwingUtilities.invokeLater拥有它,因为ActionListener代码将在EDT(Swing事件线程)上调用,无论如何。但是,我会使用SwingUtilities.invokeLater来创建GUI。

如,

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;

public class Main {

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            JFrame jf = new JFrame("hi!");
            JPanel mainPanel = new JPanel(new GridLayout());
            JButton testSystemOut = new JButton("test System.out");
            testSystemOut.addActionListener(new ActionListener() {

               @Override
               public void actionPerformed(ActionEvent e) {
                  System.out.println("this is a test");
               }
            });
            mainPanel.add(new JButton(new LongAction("Long Action")));
            mainPanel.add(new JButton(new TimerAction("Timer Action")));
            mainPanel.add(testSystemOut);
            jf.add(mainPanel);
            jf.pack();          
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setVisible(true);
         }
      });
   }

   @SuppressWarnings("serial")
   public static class LongAction extends AbstractAction {
      private LongWorker longWorker = null;

      public LongAction(String name) {
         super(name);
         int mnemonic = (int) name.charAt(0);
         putValue(MNEMONIC_KEY, mnemonic);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         setEnabled(false);
         longWorker = new LongWorker(); // create a new SwingWorker

         // add listener to respond to completion of the worker's work
         longWorker.addPropertyChangeListener(new LongWorkerListener(this));

         // run the worker
         longWorker.execute();
      }
   }

   public static class LongWorker extends SwingWorker<Void, Void> {
      private static final long SLEEP_TIME = 3 * 1000;

      @Override
      protected Void doInBackground() throws Exception {
         Thread.sleep(SLEEP_TIME);

         System.out.println("Finished with long action!");
         return null;
      }
   }

   public static class LongWorkerListener implements PropertyChangeListener {
      private LongAction longAction;

      public LongWorkerListener(LongAction longAction) {
         this.longAction = longAction;
      }

      @Override
      public void propertyChange(PropertyChangeEvent evt) {
         if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
            // if the worker is done, re-enable the Action and thus the JButton
            longAction.setEnabled(true);
            LongWorker worker = (LongWorker) evt.getSource();
            try {
               // call get to trap any exceptions that might have happened during worker's run
               worker.get();
            } catch (InterruptedException e) {
               e.printStackTrace();
            } catch (ExecutionException e) {
               e.printStackTrace();
            }
         }
      }
   }

   @SuppressWarnings("serial")
   public static class TimerAction extends AbstractAction {
      private static final int TIMER_DELAY = 3 * 1000;

      public TimerAction(String name) {
         super(name);
         int mnemonic = (int) name.charAt(0);
         putValue(MNEMONIC_KEY, mnemonic);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         setEnabled(false);
         new Timer(TIMER_DELAY, new TimerListener(this)).start();
      }
   }

   public static class TimerListener implements ActionListener {
      private TimerAction timerAction;

      public TimerListener(TimerAction timerAction) {
         this.timerAction = timerAction;
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         timerAction.setEnabled(true);
         System.out.println("Finished Timer Action!");
         ((Timer) e.getSource()).stop();
      }
   }
}

答案 1 :(得分:2)

如果要执行某些长时间运行的代码,请不要使用SwingUtilities.invokeLater(...)。在单独的正常线程中执行此操作。

Swing不是多线程的,它是事件驱动的。因此,有SwingUtilities.invokeLater(...)等方法。如果要从其他线程更改Swing-Components,则必须使用这些方法(因为Swing不是线程安全的),例如,如果要更改Button的文本。 GUI-Related的所有内容都在Swing-Thread中运行,例如:光标闪烁,来自操作系统的消息,用户命令等。 由于它是一个单独的线程,因此该线程中的每个长时间运行的代码都会阻止您的GUI。

如果您只是执行一些与GUI无关的长时运行的代码,它不应该在Swing-Event-Thread中运行,而应该在它自己的独立线程中运行。

请参阅 https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html 为什么Swing不是多线程的。