6 Swing 事件分发线程(EDT)
Swing的事件队列就类似事件队列,仅单一消费者,即一个事件分发线程。
除非你的程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。
Swing事件队列的实现机制图解
6.1 单一线程的事件队列的特性
- 将同步操作转为异步操作
- 将并行处理转换为串行顺序处理
6.2 EDT要处理所有GUI操作
- 职责明确,任何GUI请求都应该在EDT中调用
- 要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙。任何与GUI无关的处理不要由EDT执行,尤其是I/O耗时操作
7 Swing不是一个“安全线程”的API,为什么要这样设计
Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT保证。
8 invoke系方法
对非EDT的并发调用需通过invokeLater()和invokeAndWait()使请求插入到队列中等待EDT去执行。
由于Swing本身非线程安全,如果你在其他线程访问和修改GUI组件,必须使用
8.1 SwingUtilities. invokeAndWait(runnable)
同步的,它被调用结束会立即阻塞当前线程,直到EDT处理完该请求。
一般用于取得Swing组件的数据。
8.2 SwingUtilities. invokeLater(runnable)
使 doRun.run() 在AWT事件分法线程上异步执行。所有待处理的AWT事件被执行后,就会发生这种情况。当应用程序线程需要更新GUI时,应使用此方法。
在下面的示例中,invokeLater调用将Runnable对象doHelloWorld排队在事件分配线程上,然后打印一条消息。
Runnable doHelloWorld = new Runnable() { public void run() { System.out.println("Hello World on " + Thread.currentThread()); } }; SwingUtilities.invokeLater(doHelloWorld); System.out.println("This might well be displayed before the other message.");
如果从事件分发线程(例如,从JButton的ActionListener)调用invokeLater,则 doRun.run 仍将延迟,直到处理完所有未决事件。请注意,如果doRun.run 引发未捕获的异常,则事件分发线程将展开(而不是当前线程)。
从1.3版本开始,此方法只是java.awt.EventQueue.invokeLater()的封面。
与Swing的其余部分不同,可以从任何线程调用此方法。
准则
不能在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有意地避免了这类情况的发生。