当前位置 : 主页 > 编程语言 > java >

Thread专题(8) - GUI应用程序

来源:互联网 收集:自由互联 发布时间:2022-10-15
此文被笔者收录在系列文章 ​​​架构师必备(系列)​​ 中 GUI程序任务必须运行在Swing的事件线程中,几乎所有的GUI工具都实现为单线程化子系统,意味着所有的GUI的活动都被限制

此文被笔者收录在系列文章 ​​​架构师必备(系列)​​ 中

GUI程序任务必须运行在Swing的事件线程中,几乎所有的GUI工具都实现为单线程化子系统,意味着所有的GUI的活动都被限制在一个单独的线程中。

一、GUI单线程化细节

现在的GUI框架会创建一个专门的线程事件派发线程(EDT)用来处理GUI事件。多线程的GUI框架容易受死锁的影响,原因在于:

1、事件冒泡的传递机制,如果是双向的就有可能产生dead lock。

2、MVC模式不一致的锁顺序再次伴随着死锁的风险一同到来。

Swing中的线程限制

AWT和Swing提供的事件处理工具在结构上类似于单例的Executor。只有唯一的一个线程在处理GUI任务,一个任务结束才开始下一下,不会让两个任务交迭,这也是开发GUI程序重要的一点。 所有的Swing组件和数据模型都被限制在事件线程中,GUI对象不用同步,仅仅依靠线程限制来保持一致性。

Swing的组件和模型只能在事件分派线程中被创建、修改和请求。你无法从事件线程之外的地方访问表现对象,也有一些例外,如SwingUtilities.invokeLater等全局方法。下面是用Executor来模拟swing线程封闭的一个例子

public class SwingUtilities {
private static final ExecutorService exec = Executors.newSingleThreadExecutor(new SwingThreadFactory());
private static volatile Thread swingThread;

private static class SwingThreadFactory implements ThreadFactory {

public Thread newThread(Runnable r) {
swingThread = new Thread(r);
return swingThread;
}
}

public static boolean isEventDispatchThread() {
return Thread.currentThread() == swingThread;
}

public static void invokeLater(Runnable task) {
exec.execute(task);
}

public static void invokeAndWait(Runnable task)
throws InterruptedException, InvocationTargetException {
Future f = exec.submit(task);
try {
f.get();
} catch (ExecutionException e) {
throw new InvocationTargetException(e);
}
}
}

二、GUI任务执行

  • 短期任务:可以全部留在事件线程中完成,事件起源于事件线程,冒泡似的传递到应用程序提供的监听器。
  • 耗时任务:必须在事件线程以外的线程中运行它,这样才能把控制权尽快交还给事件线程,避免UI冻结。在执行完成后必须向事件线程提交另一个任务来更新状态。
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.setEnabled(false);
label.setText("busy");
exec.execute(new Runnable() {
public void run() {
try {
/* Do big computation */
} finally {
GuiExecutor.instance().execute(new Runnable() {
public void run() {
button.setEnabled(true);
label.setText("idle");
}
});
}
}
});
}
});

取消

可以通过线程中断来实现取消操作,也可以用Future的cacnel,并设置mayInterruptIfRunning参数为ture来中断一个执行运行中的任务的线程。同样也需要限定在事件线程中,限制在事件线程中其实就是限制在ActionListener适配器中。

进度和完成标识

FutureTask有一个done钩子函数,可以方便任务完成后的通知,后台Callable完成生,会调用done。这里我们可以参考BackgroundTask类的实现来完成这个功能。

三、共享数据模型

GUI程序中,除了事件线程外,唯一的线程就只有主线程,强制这些程序遵守单线程规则很容易,不要在主线程中访问数据模型和视图组件。只要阻塞不过度影响响应性,多线程操作数据的问题可以通过线程安全的数据模型来解决,如果数据模型支持精细的并发,事件线程和后台线程就能共享它,而且不存在响应性的问题。不利的一面是,不能提供一个数据快照,线程安全的数据模型必须在更新时生成事件,才能在数据变化后更新视图。

有时使用版本化数据模型,比如copy-on-write容器的迭代器。如果一个数据模型必须被多个线程共享,而且实现一个线程安全模型的尝试却由于阻塞,一致性或复杂度等原因而失败,这时可以考虑采用分拆模型设计。

应用程序的数据模型即包含表现域,又包含应用域,我们称这种应用程序是分拆模型的设计。SwingWorker类是Swing的一个辅助功能,可以用来参考,包括上面的进度和完成标识功能。

网友评论