在Swing程序中,经常能看到如下这种代码:
为何用invokeLater,而不直接调用呢? 大多数Swing的API非线程安全,不能在任意地方调用,应该只在EDT中调用。
Swing的线程安全靠事件队列和EDT来保证。
EventQueue的派发机制由单独的一个线程 - 事件派发线程(EDT)管理。 Swing将GUI请求放入一个事件队列中执行。通过EDT,使得非线程安全的Swing函数库避开了并发问题。
一个Swing程序中一般有下面三种类型的线程:
从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误
在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理
通过SwingWorker启动。应使用独立的任务线程来执行耗时计算或输入输出密集型任务。
访问网站资源、读写大树据量的文件。
任何干扰或延迟UI事件的处理只应出现在独立任务线程中。
都是非线程安全性操作。
通过SwingWorker类的管理,隔离EDT和任务线程,使它们各负其责
在计算机数据结构中,队列是一个特殊的数据结构。
队列与线程安全是无关的,不过要想将队列保证线程安全,只需要仿照生产者/消费者模式加上线程的等待/通知即可。
Swing的事件队列就类似事件队列,仅单一消费者,即一个事件分发线程。 除非你的程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。
Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT保证。
对非EDT的并发调用需通过invokeLater()和invokeAndWait()使请求插入到队列中等待EDT去执行。
由于Swing本身非线程安全,如果你在其他线程访问和修改GUI组件,必须使用
同步的,它被调用结束会立即阻塞当前线程,直到EDT处理完该请求。 一般用于取得Swing组件的数据。
使 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的其余部分不同,可以从任何线程调用此方法。
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有意地避免了这类情况的发生。