SwingUtilities的invokeLater和invokeAndWait

本文介绍了JavaSwing编程中事件派发线程(EDT)的概念,强调了界面更新应在EDT上进行以保持稳定。通过SwingUtilities的invokeLater和invokeAndWait方法,解决了耗时操作导致界面卡顿的问题,提供了使用lambda表达式的简化代码示例。
摘要由CSDN通过智能技术生成

事件派发线程(EDT)
理解SwingUtilities类作用的前提是先理解事件派发线程的概念。
当运行一个 Swing 程序时,会自动创建三个线程:

  1. 主线程,负责执行main 方法。
  2. toolkit 线程,负责捕捉系统事件,比如键盘、鼠标移动等,程序员不会有任何代码在这个线程上执行。Toolkit线程的作用是把自己捕获的事件传递给第三个线程,也就是事件派发线程。
  3. 事件派发线程(EDT,Event Dispatcher Thread),顾名思义是用来派发事件(根据事件找到对应的事件处理代码)的线程。EDT接收来自 toolkit 线程的事件,并且将这些事件组织成一个队列,EDT的工作内容就是将这个队列中的事件按照顺序派发给相应的事件监听器,并且调用事件监听器中的回调函数,这也意味着,所有的事件处理代码都是在EDT而不是主线程中执行。
    上面说到EDT中维护了一个事件的队列,并且它们是按照顺序派发的。由于事件派发是单线程的操作,所以只有等待前面事件监听器的回调函数执行完毕,才能够执行组件更新的操作,以及继续派发后面的事件。这样导致的一个后果就是:当在一个事件监听回调函数中做了耗时的操作,那么,界面会因此停住,并且界面上所有控件失效(不可触发)。
    解决这个问题的方法是:在事件处理函数中将耗时的操作放到新线程(一般称之为工作线程)中执行,而不是让其在EDT中执行。比如下面的例子。
    案例
    一个窗口,有一个按钮和一个label。点击按钮,系统将做模仿导入数据的动作,导入数据之前需要检测数据的合法性。并且,检测数据和导入数据这两个步骤都需要耗费一定的时间。
    如果没有之前说到的EDT的概念,那么你可能会这么做:
importBtn.addActionListener(new ActionListener() {
           @Override
            public void actionPerformed(ActionEvent e) {
                try{
                   lb.setText("1.检查数据合法性...");
                   Thread.sleep(3000);//模仿检测数据合法性
                   lb.setText("2.正在导入数据...");
                   Thread.sleep(4000);//模仿导入数据
                   lb.setText("3.导入成功!");
                }catch (InterruptedException e1) {
                   e1.printStackTrace();
                }
            }
        });
        

但是,如果运行一下的话,会发现现象是这样:点击按钮,界面卡住,按钮变得不可触发,直到一段时间(7秒)之后界面显示“3.导入成功”。期间并没有显示“1.检查数据合法性”和“2.正在导入数据”。
这个现象印证了上面说的理论:当事件派发线程中正在执行的事件监听函数执行完毕,才能进行UI组件的刷新操作,并且派发事件队列中的下一个。

下面是修改后的代码,将耗时的操作放在一个新的工作线程中执行:

importBtn.addActionListener(newActionListener() {
            @Override
            public voidactionPerformed(ActionEvent e) {
                new Thread(new Runnable() {//开辟一个工作线程
                    @Override
                    public void run() {
                        try {
                            lb.setText("1.检查数据合法性...");
                           Thread.sleep(3000);//模仿检测数据合法性
                            lb.setText("2.正在导入数据...");
                           Thread.sleep(4000);//模仿导入数据
                            lb.setText("3.导入成功!");
                        } catch(InterruptedException e1) {
                           e1.printStackTrace();
                        }
                    }
                }).start();
            }
        });
        

主题
下面到了SwingUtilities的内容。在swing编程中有一个编程原则:所有的界面相关的更新,都应该在 EDT 上执行,否则会导致界面绘制出现不稳定性错误。这也就意味着上面代码的lb.setText(“2.正在导入数据…”);应该在EDT中,而非新的工作线程中执行。这样就出现了一个矛盾:耗时的操作必须要在工作线程中执行,否则会出现界面刷新不及时和卡顿的现象,而工作线程中的界面刷新代码又会导致界面绘制的不稳定。
SwingUtilities可以解决这个矛盾。SwingUtilities的invokeLater和invokeAndWait方法可以将一个可执行对象(Runnable)实例追加到EDT的可执行队列中。那么最后的代码应该是这样的:

importBtn.addActionListener(newActionListener() {
            @Override
            public voidactionPerformed(ActionEvent e) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                           SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public voidrun() {
                                    lb.setText("1.检查数据合法性...");
                                }
                            });
                           Thread.sleep(3000);//模仿检测数据合法性
                           SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public voidrun() {
                                   lb.setText("2.正在导入数据...");
                                }
                            });
                           Thread.sleep(4000);//模仿导入数据
                            SwingUtilities.invokeLater(newRunnable() {
                                @Override
                                public voidrun() {
                                   lb.setText("3.导入成功!");
                                }
                            });
                        } catch(InterruptedException e1) {
                           e1.printStackTrace();
                        }
                    }
                }).start();
            }
        });
        

结论
通过上面内容可以总结以下两个swing编程原则:
所有的界面相关的更新,都应该在 EDT 上执行
而耗时的后台运行,不应该在 EDT 上执行

lambda表达式重写上面的代码:

 importBtn.addActionListener(e -> {
            new Thread(() -> {
                try {
                   SwingUtilities.invokeLater(() -> lb.setText("1.检查数据合法性..."));
                    Thread.sleep(3000);//模仿检测数据合法性
                    SwingUtilities.invokeLater(()-> lb.setText("2.正在导入数据..."));
                    Thread.sleep(4000);//模仿导入数据
                   SwingUtilities.invokeLater(() -> lb.setText("3.导入成功!"));
                } catch (InterruptedExceptione1) {
                    e1.printStackTrace();
                }
            }).start();
        });

原文链接:简书博客:SwingUtilities的invokeLater和invokeAndWait

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我明白了,以下是您所问的答案: SwingUtilities.invokeLater() 和 SwingUtilities.invokeAndWait() 都可以在事件分派线程(Event Dispatch Thread)上执行 Runnable 对象。区别在于,invokeLater() 把 Runnable 对象放到事件分派线程的队列中等待执行,而 invokeAndWait() 则是阻塞当前线程,直到 Runnable 对象执行完成。 下面是一个使用 SwingUtilities.invokeLater() 的例子: ```java import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class Example { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("Hello, World!"); JLabel label = new JLabel("Hello, World!"); frame.getContentPane().add(label); frame.pack(); frame.setVisible(true); } }); } } ``` 这个例子创建了一个 JFrame,在 JFrame 中添加了一个 JLabel,并把 JFrame 显示出来。使用 invokeLater() 保证了这些操作都在事件分派线程上执行。 下面是一个使用 SwingUtilities.invokeAndWait() 的例子: ```java import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class Example { public static void main(String[] args) throws Exception { SwingUtilities.invokeAndWait(new Runnable() { public void run() { JFrame frame = new JFrame("Hello, World!"); JLabel label = new JLabel("Hello, World!"); frame.getContentPane().add(label); frame.pack(); frame.setVisible(true); } }); System.out.println("Done!"); } } ``` 这个例子也创建了一个 JFrame,在 JFrame 中添加了一个 JLabel,并把 JFrame 显示出来。使用 invokeAndWait() 保证了这些操作都在事件分派线程上执行,并且会阻塞当前线程直到 JFrame 显示完成。最后,它打印了一条消息,表示所有操作都已完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值