下图是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,请求也不会去执行。看源码:
[java]
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);
}
}
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有意地避免了这类情况的发生。