SwingUtilities的invokeLater和invokeAndWait

原文地址

Swing程序的线程处理

前言

因为很多人会见到一些源代码中调用SwingUtilities的invokeLater或者invokeAnd-Wait方法,但是却不理解它们到底起到了什么作用,本文的目标就是让你理解这两个方法的意义。本文是swing编程基础且不可越过的一节。而且,如你所见,本文的副标题是“Swing程序的线程处理”,其实这是对本文内容更好的概括。

事件派发线程(EDT)

理解SwingUtilities类作用的前提是先理解事件派发线程的概念。

当运行一个 Swing 程序时,会自动创建三个线程。

1.主线程,负责执行main
方法。

2. toolkit 线程,负责捕捉系统事件,比如键盘、鼠标移动等,程序员不会有任何代码在这个线程上执行。Toolkit线程的作用是把自己捕获的事件传递给第三个线程,也就是事件派发线程。

3. 事件派发线程(EDT,Event Dispatcher Thread),顾名思义是用来派发事件(根据事件找到对应的事件处理代码)的线程。EDT接收来自 toolkit 线程的事件,并且将这些事件组织成一个队列,EDT的工作内容就是将这个队列中的事件按照顺序派发给相应的事件监听器,并且调用事件监听器中的回调函数,这也意味着,所有的事件处理代码都是在EDT而不是主线程中执行。

上面说到EDT中维护了一个事件的队列,并且它们是按照顺序派发的。由于事件派发是单线程的操作,所以只有等待前面事件监听器的回调函数执行完毕,才能够执行组件更新的操作,以及继续派发后面的事件。这样导致的一个后果就是:当在一个事件监听回调函数中做了耗时的操作,那么,界面会因此停住,并且界面上所有控件失效(不可触发)。

解决这个问题的方法是:在事件处理函数中将耗时的操作放到新线程(一般称之为工作线程)中执行,而不是让其在EDT中执行。比如下面的例子。

案例

一个窗口,有一个按钮和一个label。点击按钮,系统将做模仿导入数据的动作,导入数据之前需要检测数据的合法性。并且,检测数据和导入数据这两个步骤都需要耗费一定的时间。

如果没有之前说到的EDT的概念,那么你可能会这么做:

importBtn.addActionListener(newActionListener() {
           @Override
            publicvoid 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();
            }
        });

关于invokeLater和invokeAndWait的区别,请自行搜索。

结论

通过上面内容可以总结以下两个swing编程原则

所有的界面相关的更新,都应该在 EDT 上执行

而耗时的后台运行,不应该在 EDT 上执行

扩展

对于java8来说上面的代码可以用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();
        });
时间: 2024-10-19 10:43:05

SwingUtilities的invokeLater和invokeAndWait的相关文章

java invokelater 以及invokeandwait

SwingUtilities中invokeLater和invokeAndWait介绍   在Java中Swing是线程不安全的,是单线程的设计,这样的造成结果就是:只能从事件派发线程访问将要在屏幕上绘制的Swing组件.事件派发线程是调用paint和update等回调方法的线程,它还是事件监听器接口中定义的事件处理方法,例如,ActionListener中的actionPerformed方法在事件派发线程中调用.   Swing是事件驱动的,所以在回调函数中更新可见的GUI是很自然的事情,比如,

Java中事件分发线程(EDT)与SwingUtilities.invokeLater相关总结

前言:这篇文章严格来说不算原创,算是我对这方面知识的一点小结,素材来至其他网友.当然我在我写的C段查询工具也用到了这方面的东西,不过由于代码太多不方便用作事例,因此用了他人的素材总结一下,望理解O(∩_∩)O~ 一 Swing线程基础 一个Swing程序中一般有下面三种类型的线程:    * 初始化线程(Initial Thread)    * UI事件调度线程(EDT)    * 任务线程(Worker Thread)每个程序必须有一个main方法,这是程序的入口.该方法运行在初始化或启动线程

【转】Swing 与EDT线程

在Swing程序中,经常能看到如下这种代码: SwingUtilities.invokeLater(new Runnable(){ @Override public void run() { textField1.setText("element changed!"); textField1.setForeGround(Color.RED); } }); 为什么要用SwingUtilities.invokeLater,而不直接调用呢?因为大多数SwingAPI是非线程安全的,也就是说不

Java性能提示(全)

http://www.onjava.com/pub/a/onjava/2001/05/30/optimization.htmlComparing the performance of LinkedLists and ArrayLists (and Vectors) (Page last updated May 2001, Added 2001-06-18, Author Jack Shirazi, Publisher OnJava). Tips: ArrayList is faster than

swing线程机制

在介绍swing线程机制之前,先介绍一些背景概念. 背景概念 同步与异步:     同步是指程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被阻塞(block)直到请求完成.     异步是当前程序发起请求后立即返回,当前程序不会立即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理. 串行与并行: 串行是指多个要处理的请求按照顺序执行,处理完一个再处理下一个. 并行可以理解为并发,指的是同时处理多个请求(实际上我们只能理论上这么理解,特别是CPU数目少于线程

java swing 防抖机制

从问题开始说. 我们产品的 操作台(workbench) 是一个java swing程序. 有一个event handler的代码如下: public void actionPerformed(ActionEvent e) { // 做耗时的工作, 如插入数据库 } 现在的一个问题在于, 如果用户连续点了两次button, 则event handler被执行两次, 则用户会得到 主键重复错误. 简单的想法是 在event handler的开头结尾把 对应的ok button给disable/en

Java中 EvenQueue.invokeLater用法

在Java中Swing是线程不安全的,是单线程的设计,这样的造成结果就是:只能从事件派发线程访问将要在屏幕上绘制的Swing组件.事件派发线程是调用paint和update等回调方法的线程,它还是事件监听器接口中定义的事件处理方法,例如,ActionListener中的actionPerformed方法在事件派发线程中调用.          Swing是事件驱动的,所以在回调函数中更新可见的GUI是很自然的事情,比如,有一个按钮被按下,项目列表需要更新时,则通常在与该按钮相关联的事件监听器的a

Java Swing中有关事件机制

看到过两种方式启动主窗体的代码: 方式1: java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new MainJFrame().setVisible(true); } }); 方式2 javax.swing.SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new MainJFrame().setVisible(

小程序[邮箱提取器-EmailSplider]总结

1.背景情况 学东西做快的是付诸实践,写这个小程序的目的就是为了综合运用各个知识点,从而提升学习的效果. 2.涉及知识 A.Swing 的布局 B.Swing中,线程访问UI C.URLConnection 读取网页源码 D.IO流的基本操作 E.正则表达式的基本使用 F.Window Builder插件的发现和使用 G.jar包的制作和双击jar运行的修复 H.jdk1.8的新特性,优雅的 lambda 语法 3.效果图 1.windows上运行效果 ↓ 2.linux上运行效果 ↓ 4.源代