[转:http://blog.csdn.net/vking_wang/article/details/8992463]
在Swing程序中,经常能看到如下这种代码:
- SwingUtilities.invokeLater(new Runnable(){
- @Override
- public void run() {
- textField1.setText("element changed!");
- textField1.setForeGround(Color.RED);
- }
- });
为什么要用SwingUtilities.invokeLater,而不直接调用呢?因为大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。
EventQueue的派发机制由单独的一个线程管理,这个线程称为事件派发线程(EDT)。和其他很多桌面API一样,Swing将GUI请求放入一个事件队列中执行。
通过EDT,使得不具备线程安全的Swing函数库避开了并发访问的问题。
背景概念
要了解EDT,首先需要了解一些背景概念:
同步与异步:
同步是程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被block住直到请求完成。
异步是当前程序发起请求后立即返回,当前程序不会立即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理。
串行与并行:
串行是指多个要处理请求顺序执行,处理完一个再处理下一个;
并行可以理解为并发,是同时处理多个请求(实际上我们只能理解为是这样,特别是CPU数目少于线程数的机器而言,真正意义的并发是不存在的,各个线程只是断断续续地交替地执行)。
下图演示了串行与并行的机制。可以这么说,在引入多线程之前,对于同一进程或者程序而言执行的都是串行操作。
串行:
并行:
生产者/消费者模式:
可以想象这样一副场景,某车间的一条传送带,有一个或多个入口不断产生待加工的货物,这种不断产生货物的称为生产者;传送带的末端是一个或多个工人在加工货物,称作消费者。有时由于传送带上没有足够的货物使得某一工人暂时空闲,有时又由于部分货物需加工的时间较长出现传送带上待加工的货物堆积。
如果用Java实现一个简单的生产者消费者模型,利用线程的等待/通知机制很容易实现。
- public class SyncQueue {
- private List buffer = new ArrayList();
- //消费
- public synchronized Object pop() {
- Object e;
- while (buffer.size() == 0) {
- try {
- wait();
- } catch (InterruptedException e1) {
- // ignore it
- }
- }
- e = buffer.remove(0);
- return e;
- }
- //生产
- public synchronized void push(Object e) {
- notifyAll();
- buffer.add(e);
- }
- }
事件队列:
在计算机数据结构中,队列是一个特殊的数据结构。其一、它是线性的;其二、元素是先进先出的,也就是说进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才能执行,队列的处理方式是执行完一个再执行下一个。
队列与线程安全是两个不同的概念,如果要将队列加上线程安全的特性,只需要仿照上述生产者/消费者加上线程的等待/通知即可。
Swing程序中的线程
一个Swing程序中一般有下面三种类型的线程:
- 初始化线程(Initial Thread)
- UI事件调度线程(EDT)
- 任务线程(Worker Thread)
初始化线程:每个程序必须有一个main方法,这是程序的入口。该方法运行在初始化或启动线程上。初始化线程读取程序参数并初始化一些对象。在许多Swing程序中,该线程主要目的是启动程序的图形用户界面(GUI)。创建UI的点,也就是程序开始将控制权转交给UI时的点。一旦GUI启动后,对于大多数事件驱动的桌面程序来说,初始化线程的工作就结束了。
EDT:Swing程序只有一个EDT,该线程负责GUI组件的绘制和更新,通过调用程序的事件处理器来响应用户交互。所有事件处理都是在EDT上进行的,程序同UI组件和其基本数据模型的交互只允许在EDT上进行,所有运行在EDT上的任务应该尽快完成,以便UI能及时响应用户输入。
Swing编程时应该注意:
- 从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误。——必须通过EDT刷新组件
- 在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理。——不能在EDT中执行其他耗时操作
- 应使用独立的任务线程来执行耗时计算或输入输出密集型任务,比如同数据库通信、访问网站资源、读写大树据量的文件。——耗时操作应放到独立的任务线程中,通过SwingWorker启动
SwingWorker类帮你管理任务线程和EDT之间的交互,尽管SwingWorker不能解决并发线程中遇到的所有问题,但的确有助于分离SwingEDT和任务线程,使它们各负其责:对于EDT来说,就是绘制和更新界面,并响应用户输入;对于任务线程来说,就是执行和界面无直接关系的耗时任务和I/O密集型操作。
Swing事件分发线程
Swing的事件队列就类似于上述的事件队列(基本原理相似,但是Swing内部实现会做些优化),说它是单线程图形工具包指的是仅有单一消费者,也就是常说的事件分发线程(EDT),一般来讲,除非你的应用程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。
下图是Swing事件队列的实现机制:
很显然,如果在加工某一个货物上花费很长的时间,那么后续的货物只好等待。
对于单一线程的事件队列来说有两个非常突出的特性:
- 将同步操作转为异步操作。
- 将并行处理转换为串行顺序处理。
EDT要处理所有GUI操作,它是职责分明且非常忙碌的。也就是说你要记住两条原则:
- 职责分明,任何GUI请求都应该在EDT中调用。
- 需要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙,所以任何与GUI无关的处理不要由EDT来负责,尤其是I/O这种耗时的操作。
上面说过Swing不是一个“安全线程”的API,为什么要这样设计?再回看上图就会明白:Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。
invokeLater和invokeAndWait
由于Swing自身不是线程安全,如果你在其他线程访问和修改GUI组件,那么你必须要使用SwingUtilities. invokeAndWait(runnable), SwingUtilities. invokeLater(runnable)。也就是说对非EDT的并发调用需通过invokeLater()和invokeAndWait()使请求插入到队列中等待EDT去执行。
- invokeLater(runnable)方法是异步的,它会立即返回,具体何时执行请求并不确定,所以命名invokeLater是稍后调用。
- invokeAndWait(runnable)方法是同步的,它被调用结束会立即block当前线程(调用invokeAndWait的那个线程)直到EDT处理完那个请求。invokeAndWait一般的应用是取得Swing组件的数据。
invokeAndWait有非常重要的一条准则是:它不能在EDT中被调用,否则程序会抛出Error,请求也不会去执行。看源码:
- public static void invokeAndWait(Runnable runnable)
- throws InterruptedException, InvocationTargetException {
- //不能在EDT中调用invokeAndWait
- if (EventQueue.isDispatchThread()) {
- throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
- }
- class AWTInvocationLock {}
- Object lock = new AWTInvocationLock();
- InvocationEvent event =
- new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock,
- true);
- synchronized (lock) {
- //添加进事件队列
- Toolkit.getEventQueue().postEvent(event);
- //block当前线程
- lock.wait();
- }
- Throwable eventThrowable = event.getThrowable();
- if (eventThrowable != null) {
- throw new InvocationTargetException(eventThrowable);
- }
- }
如果invokeAndWait在EDT中调用,那么首先将请求压进队列,然后EDT便被block,等待请求结束通知它继续运行。
而实际上请求将永远得不到执行,因为它在等待队列的调度使EDT执行它,这就陷入一个僵局:EDT等待请求先执行,请求又等待EDT对队列的调度。彼此等待对方释放锁是造成死锁的四类条件之一。Swing有意地避免了这类情况的发生。
参考:
深入浅出Swing事件分发线程: http://space.itpub.net/13685345/viewspace-374940
使用SwingWorker: http://blog.sina.com.cn/s/blog_4b6047bc010007so.html
相关推荐
Swing线程机制是Swing库中的关键概念,它确保了所有对Swing组件的操作都在正确的线程——事件分发线程(Event Dispatch Thread,简称EDT)上执行,以避免线程安全问题和界面更新不一致的情况。`SwingUtilities....
Swing组件默认不是线程安全的,因此必须通过`SwingUtilities.invokeLater()`、`SwingUtilities.invokeAndWait()`或使用`SwingWorker`类来确保所有对Swing组件的操作都在事件分发线程中执行。这些技术的应用可以有效...
Swing是单线程模型,这意味着所有的UI更新都必须在称为“事件调度线程”(Event Dispatch Thread, EDT)的线程中执行。这是为了确保界面始终是响应式的,避免因为长时间的计算任务阻塞用户界面。`EventQueue`就是这...
- **事件分发线程 (EDT)**:当主线程显示第一个窗口时,会创建EDT。EDT是负责处理用户交互、事件处理和界面渲染的线程。所有的事件回调(如`actionPerformed`)和绘图操作(如`paintComponent`)都在这个线程中执行...
SwingUtilities.invokeLater方法用于确保Swing组件的创建和操作在事件分发线程(EDT)上执行,这是因为Swing是单线程的,所有的用户界面更新都需要在EDT上进行以避免线程安全问题。 ### JavaFX的SwingNode组件 ...
为了确保UI组件的更新不会导致界面卡顿或出现不可预料的行为,所有的UI更新操作都应该在事件分发线程(Event Dispatch Thread, EDT)中执行。 1. **EDT的作用**:EDT负责处理所有与用户交互相关的操作,如鼠标点击、...
- **事件处理机制不当**:Swing使用事件分发线程(Event Dispatch Thread,简称EDT)来处理用户界面的更新和用户事件。当程序员将耗时的操作(如复杂的计算或大量数据处理)放置在事件处理函数中时,会导致EDT被长...
为了确保界面的更新是安全的,我们需遵守Java Swing的多线程规则,即所有的UI更新操作必须在事件分发线程(Event Dispatch Thread, EDT)上执行。因此,我们需要使用`SwingUtilities.invokeLater()`或`SwingWorker`...
考虑到GUI的更新需要在事件分发线程(Event Dispatch Thread, EDT)中进行,以保持界面的同步,发牌线程可能需要通过`SwingUtilities.invokeLater()`或`invokeAndWait()`方法来调度,确保在正确的线程上执行。...
5. **线程管理**: 在Java中,GUI操作应在事件分发线程(Event Dispatch Thread, EDT)中执行,以确保界面的响应性和一致性。因此,我们需要确保计时器的回调函数运行在EDT上,这可以通过调用`SwingUtilities....
3. **线程处理**:在Java中,为了保持用户界面的响应性,GUI操作通常应在事件 dispatch thread(EDT,事件分发线程)上进行。而鼠标跟踪可能涉及一个后台线程持续监控鼠标的坐标变化。这需要理解Java的并发概念,...
5. **Event Dispatch Thread (EDT)**:在Java GUI中,所有的界面更新操作都应在事件分发线程(EDT)中执行,以确保界面的线程安全。因此,任何修改窗口属性的代码都需要在`SwingUtilities.invokeLater()`或`EventQueue...
为了确保用户界面的响应性,绘图操作应该在事件分发线程(Event Dispatch Thread, EDT)之外进行。因此,`Timer`的回调可能会在新的线程中执行,然后通过`SwingUtilities.invokeLater()`或`invokeAndWait()`方法...
- **Swing的线程模型**:Swing组件必须在事件分发线程(Event Dispatch Thread, EDT)中更新,以避免线程安全问题。因此,可能需要使用`SwingUtilities.invokeLater()`或`SwingWorker`来异步执行耗时的操作。 6. *...
Swing组件是在事件分发线程(EDT)上运行的,以确保GUI的线程安全。Swing组件包括JFrame、JPanel、JLabel、JTextField、JTextArea、JButton等,这些组件被用来构建窗口界面。 在本例的源代码中,moneyMan类继承自...
7. **Swing应用程序的启动**:程序通常通过` javax.swing.SwingUtilities.invokeLater(Runnable r)`来启动,确保所有UI操作都在事件调度线程(Event Dispatch Thread, EDT)上执行,遵循Swing的线程安全规则。...
6. ** SwingUtilities.invokeLater()**:在多线程环境中,为了确保控件的更新在事件 dispatch 线程(EDT)中进行,防止出现线程安全问题,通常会使用此方法来确保UI操作的正确性。 7. **自定义事件**:除了内置的...
3. **线程同步**:在GUI中,所有的图形更新操作必须在事件分发线程(Event Dispatch Thread, EDT)中进行,以避免界面冻结。因此,你需要确保更新时钟的代码是EDT安全的,这可能通过`SwingUtilities.invokeLater()`...