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

前言:这篇文章严格来说不算原创,算是我对这方面知识的一点小结,素材来至其他网友。当然我在我写的C段查询工具也用到了这方面的东西,不过由于代码太多不方便用作事例,因此用了他人的素材总结一下,望理解O(∩_∩)O~

一 Swing线程基础

一个Swing程序中一般有下面三种类型的线程:
    * 初始化线程(Initial Thread)
    * UI事件调度线程(EDT)
    * 任务线程(Worker Thread)
每个程序必须有一个main方法,这是程序的入口。该方法运行在初始化或启动线程上。初始化线程读取程序参数并初始化一些对象。在许多Swing程序中,该线程主要目的是启动程序的图形用户界面(GUI)。一旦GUI启动后,对于大多数事件驱动的桌面程序来说,初始化线程的工作就结束了。

Swing程序只有一个用EDT,该线程负责GUI组件的绘制和更新,通过调用程序的事件处理器来响应用户交互。所有事件处理都是在EDT上进行的,程序同UI组件和其基本数据模型的交互只允许在EDT上进行,所有运行在EDT上的任务应该尽快完成,以便UI能及时响应用户输入。

Swing编程时应该注意以下三点:
1.从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误。
2.在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理。
3.应使用独立的任务线程来执行耗时计算或输入输出密集型任务,比如同数据库通信、访问网站资源、读写大树据量的文件。
总之,任何干扰或延迟UI事件的处理只应该出现在独立任务线程中;在初始化线程或任务线程同Swing组件或其缺省数据模型进行的交互都是非线程安全性操作。

二 使用合适线程

初始化线程运行程序的main方法,该方法能处理许多任务。但在典型的Swing程序中,其主要任务就是创建和运行应用程序的界面。创建UI的点,也就是程序开始将控制权转交给UI时的点,往往是同EDT交互出现问题的第一个地方。

许多程序使用下面方法启动界面,但这是错误的启动UI界面的方法:
public class MainFrame
extends javax.swing.JFrame {
 …

public static void main(String[] args)
{
   new MainFrame().setVisible(true);
 }
}

尽管这种错误出现在开始,但仍然违反了不应在EDT外的其他线程同Swing组件交互的原则。这个错误尤其容易犯,线程同步问题虽然不是马上显示出来,但是还要注意避免这样书写。

正确启动UI界面应该如下:

public class MainFrame extends javax.swing.JFrame
{
 …

public static void main(String[] args)
{
   SwingUtilities.invokeLater(new Runnable()
{
     public void run()
{
       new
MainFrame().setVisible(true);
     }
   });
 }
}

使用NetBeans IDE的开发者应该对这段代码很熟悉,NetBeans通常会自动生成这段代码。这段启动代码虽然和SwingWorker没有直接关系,但是这个编程范式很重要。SwingUtilities类包含一些静态方法帮你同UI组件交互,其中invokeLater方法意思是在EDT上执行其Runnable任务。Runnable接口定义了可作为独立线程执行的任务。

在初始化线程中使用invokeLater方法能正确的初始化程序界面。就像前面文章所提到的,此方法是异步执行的,也就是说调用会立即返回。创建界面后,大部分初始化线程基本上就结束了。

通常有两种办法调用此方法:
* SwingUtilities.invokeLater
* EventQueue.invokeLater
两个方法都是正确的,选择任何一个都可以。实际上,SwingUtilities版只是一个薄薄的封装方法,它直接转而调用EventQueue.invokeLater。因为Swing框架本身经常调用SwingUtilities,使用SwingUtilities可以减少程序引入的类。

另种将任务放到EDT执行的方法是SwingUtilities.invokeAndWait,不像invokeLater,invokeAndWait方法是阻塞执行的,它在EDT上执行Runnnable任务,直到任务执行完了,该方法才返回调用线程。

invokeLater和invokeAndWait都在事件派发队列中的所有事件都处理完之后才执行它们的Runnable任务,也就是说,这两个方法将Runnable任务放在事件队列的末尾。

注意:虽然可以在其他线程上调用invokeLater,也可以在EDT上调用invokeLater,但是千万不要在EDT线程上调用invokeAndWait方法!很容易理解,这样做会造成线程竞争,程序就会陷入死锁。

三 进一步理解事件派发线程(EDT)

当运行一个 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组件的刷新操作,并且派发事件队列中的下一个。

四 使用SwingUtilities.invokeLater实现的一个进度条实例

运行后截图如下:

完整代码:

package test;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class SwingThreadTest2 extends JFrame {
	private static final long serialVersionUID = 1L;
	private static final String STR = "Completed : ";
	private JProgressBar progressBar = new JProgressBar();
	private JTextField text = new JTextField(10);
	private JButton start = new JButton("Start");
	private JButton end = new JButton("End");
	private boolean flag = false;
	private int count = 0;

	private GoThread t = null;

	private Runnable run = null;// 更新组件的线程

	public SwingThreadTest2() {
		this.setLayout(new FlowLayout());
		add(progressBar);
		text.setEditable(false);
		add(text);
		add(start);
		add(end);
		start.addActionListener(new Start());
		end.addActionListener(new End());

		run = new Runnable() {// 实例化更新组件的线程
			public void run() {
				progressBar.setValue(count);
				text.setText(STR + String.valueOf(count) + "%");
			}
		};
	}

	private class Start implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			flag = true;
			if (t == null) {
				t = new GoThread();
				t.start();
			}
		}
	}

	class GoThread extends Thread {
		public void run() {
			while (count < 100) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (flag) {
					count++;
					SwingUtilities.invokeLater(run);// 将对象排到事件派发线程的队列中
				}
			}
		}
	}

	private class End implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			flag = false;
		}
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				SwingThreadTest2 fg = new SwingThreadTest2();
				fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				fg.setSize(300, 100);
				fg.setVisible(true);

			}
		});

	}
}

附:参考文档:

1 使用SwingWorker之一  http://blog.sina.com.cn/s/blog_4b6047bc010007so.html

2 SwingUtilities的invokeLater和invokeAndWait  http://blog.csdn.net/yanwushu/article/details/39434159

3 [转]方法SwingUtilities.invokeLater()的作用  http://blog.csdn.net/hsnxyc/article/details/6501284

时间: 2024-10-04 13:55:45

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

关于JAVA中事件分发和监听机制实现的代码实例-绝对原创实用

http://blog.csdn.net/5iasp/article/details/37054171 文章标题:关于JAVA中事件分发和监听机制实现的代码实例 文章地址: http://blog.csdn.net/5iasp/article/details/37054171 作者: javaboy2012Email:[email protected]qq:    1046011462 一.场景假设 假设有博客系统中需要实现如下功能: 系统中用户发布文章,修改文章,删除文章时,需要一些相关的操作

使用泛型SwingWorker与EDT事件分发线程保持通讯

为什么要使用SwingWorker 在swing开发中,如果一个应用程序,执行一些任务,需要大量的时间来完成,比如下载一个大文件或执行一个复杂的数据库查询. 我们假设这些任务是由用户使用一个按钮触发的.在单线程应用程序,用户单击按钮,进入计算的过程,然后等待任务完成之前,所有的事件都在主线程EDT线程进行. 但如果某些任务耗时很长,用户将甚至不能在中途取消任务,应用程序必须响应只有当长任务完成.不幸的是,许多应用程序显着这样的行为和用户感到沮丧,程序仿佛卡死一样. 多线程可以解决这个问题.它使应

Qt中事件分发源代码剖析(一共8个步骤,顺序非常清楚:全局的事件过滤器,再传递给目标对象的事件过滤器,最终传递给目标对象)

Qt中事件分发源代码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,并且进行分发,这些都是在exec中进行的.下面举例说明: 1)首先看看下面一段示例代码: [cpp] view plaincopy int main(int argc, char *argv[]) { QApplication a(argc, argv); MouseEvent w; w.show(); return a.exec(); } 2)a.exec进入事件循环,调用的是QAppli

Java中的守护线程

Java中的守护线程 Java中的守护线程与UNIX中的守护线程概念不同,UNIX中的守护线程相当于一项服务,一直运行在后台,而Java中的守护线程是这样定义的: A daemon thread is a thread, that does not prevent the JVM from exiting when the program finishes but the thread is still running. 也就是说,当程序中只有守护线程时,JVM就会自动退出,典型的守护线程就是垃

线程基础:线程(3)——JAVA中的基本线程操作(中)

(接上文<线程基础:线程(2)--JAVA中的基本线程操作(上)>) 1-4.注意synchronized关键字的使用 在前面的文章中我们主要讲解的是线程中"对象锁"的工作原理和操作方式.在讲解synchronized关键字的时候,我们还提到了synchronized关键字可以标注的位置.大家经常看到相当部分的网贴,在它们的代码示例中将synchronized关键字加载到代码的方法体上,然后告诉读者:这个操作是线程安全的.代码可能如下: /** * 这个类的class对象进

JAVA学习篇--ThreadLocal,Java中特殊的线程绑定机制

在DRP项目中,我们使用了ThreadLocal来创建Connection连接,避免了一直以参数的形式将Connection向下传递(传递connection的目的是由于jdbc事务要求确保使用同一个connection连接).那么ThreadLocal是如果做到的呢?它和同步锁的不同在哪里? 是什么: 对于ThreadLocal看英文单词我们很容易理解为一个线程的本地实现,但是它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLoc

Qt中事件分发源代码剖析

Qt中事件分发源代码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,并且进行分发,这些都是在exec中进行的. 下面举例说明: 1)首先看看下面一段示例代码: int main(int argc, char *argv[]) { QApplication a(argc, argv); MouseEvent w; w.show(); return a.exec(); } 2)a.exec进入事件循环,调用的是QApplication::exec(): int

Java中要使用 BigDecimal(定点数) 进行小数的相关操作,避免使用float等浮点数

Java中要使用 BigDecimal(定点数) 进行小数的相关操作,避免使用float等浮点数,因为 float, double等浮点的存储和操作(比如:相加,相减...)存在误差(7.22f - 7.0f = 0.21999979 而不是 0.22). 下面以 BigDecimal 进行减法为例: /**  * 精确的减法  */ public static double subtract(double v1, double v2) { BigDecimal d1 = new BigDeci

关于Java中进程和线程的详解

一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消.反映了一个程序在 一定的数据 集上运行的全部动态过程.通过进程控制块(PCB)唯一的标识某个进程.同时进程占据着相应的资源(例如包 括cpu的使用 ,轮转时间以及一些其它设备的权限).是系统进行资源分配和调度的一个独立单位. 程序和进程之间的主要区别在于: 状态         是否具有资源