图形用户界面应用程序:
Swing的数据结构不是线程安全的,因此必须将它们限制在事件线程中。
几乎所有的GUI工具包(awt ,swing)都被实现为单线程子系统。
为什么GUI是单线程的
当前的GUI框架通过一个事件分发线程(Event Dispatch Thread, EDT)来处理GUI事件。
单线程的GUI框架通过线程封闭机制来实现线程安全性。
串行事件处理:
避免任务执行的时间过长,导致GUI无法响应其他事件。
Swing中的线程封闭机制:
Swing组件及数据模型对象(TableModel, TreeModel)也通过线程封闭机制实现线程安全性。
Swing中也存在单线程规则以外的情况:
1. SwingUtilities.isEventDispatchThread, 判断当前线程是否是事件线程。
2. SwingUtilities.invokeLater, 可以将一个Runnable任务调度到事件线程中执行(可在任务线程中调度)。
3. SwingUtilities.invokeAndWait, 可以将一个Runnable任务调度到事件线程中执行,并阻塞当前线程直到任务完成(只能从非GUI线程中调用)。
4. 所有Repaint, Revalidation请求插入队列的方法(任意线程中调用)。
5. 所有添加或删除监听器的方法(可以在任务线程中调用,但监听器本身一定在事件线程中调用)。
短时间的GUI任务:
短时间任务可以在事件线程中执行,长时间任务则放在另一个线程中执行。
模型对象与数据对象的控制流。
长时间的GUI任务:
对于长时间任务,可以使用缓存线程池(Executors.newCachedThreadPool)。
例如在事件线程中执行任务:
private static ExecutorService exec = Executors.newCachedThreadPool();
...
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exec.execute(new Runnable() { //执行任务
@Override
public void run() {
doMuchComptation(); //长时间任务
}
});
}
});
我们还希望任务执行前后能有一些响应反馈:
public void actionPerformed(ActionEvent e) {
button.setText("Loading..."); //设置button表现
button.setEnabled(false);
exec.execute(new Runnable() { //执行任务
@Override
public void run() {
try {
doMuchComptation();
} finally{
exec.execute(new Runnable() {
@Override
public void run() { //恢复button表现
button.setText("Load");
button.setEnabled(true);
}
});
}
}
});
}
取消:
当任务运行过长时间,用户想取消它,比较简单的办法就是Future。
private static ExecutorService exec = Executors.newCachedThreadPool();
private static Future> runningTask = null; //任务
startBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (runningTask != null){
runningTask = exec.submit(new Runnable() {
@Override
public void run() {
while (moreWork()){
if (Thread.currentThread().isInterrupted()){//若中断了
doClean();
break;
}
doWork();
}
}
});
}
}
});
stopBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (runningTask != null){
runningTask.cancel(true); //取消任务
}
}
});
进度标识和完成标识
当任务执行中,我们希望知道进度信息;当任务完成时,我们希望得到一个完成通知。
/**
* 支持取消,完成通知及进度通知的任务
*/
public abstract class BackgroundTask implements Runnable, Future {
private final FutureTask computation = new Computation();
/**
* 计算任务
*/
private class Computation extends FutureTask{
public Computation() {
super(new Callable() {
@Override
public T call() throws Exception {
return BackgroundTask.this.compute();
}
});
}
@Override
protected final void done(){
GuiExecutor.instance().execute(new Runnable() {
@Override
public void run() {
T value = null;
Throwable thrown = null;
boolean cancelled = false;
try {
value = get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
thrown = e.getCause();
} catch (CancellationException e){
cancelled = true;
} finally{
onCompletion(value, thrown, cancelled);
}
}
});
}
}
protected void setProgress(final int current, final int max){
GuiExecutor.instance().execute(new Runnable() {
@Override
public void run() {
onProgress(current, max);
}
});
}
private void onProgress(int current, int max) {}
private void onCompletion(T value, Throwable thrown,
boolean cancelled) {}
/**
* 计算过程
*/
protected abstract T compute();
...//Future其他方法
}
SwingWorker:
在Java Swing中我们可以通过SwingWorker来实现类似上面的取消,完成通知,进度指示等。
看到SwingWorker, 其实就如上面的BackGroundTask:
public abstract class SwingWorker implements RunnableFuture {
...//实现RunnableFuture接口, T: doInBackground返回的结果类型, V: publish, process的参数类型
} 使用实例可以如下:
SwingWorker worker
= new SwingWorker(){
@Override
protected String doInBackground() throws Exception {
System.out.println("任务开始");
// do some computation
publish(20);
publish(30);
//...
publish(100);
return "返回计算结果";
}
@Override
protected void process(List chunks) {
//接收publish发送的数据
for (Integer chunk : chunks){
//update ui, for example progress bar
System.out.println(chunk);
}
}
@Override
protected void done() {
System.out.println("任务完成");
// do some state change
}
};
GuiExecutor.instance().execute(worker);
worker.get(); //block to wait results.
共享数据模型
Swing中的数据模型有TableModel和TreeModel等,且都被封闭在事件线程中。
线程安全的数据模型:
线程安全的数据模型必须在更新模版时产生事件,这样视图才能在数据发生变化后进行更新。
分解数据模型:
如果程序中既包含用于表示的数据模型(如TableModel,TreeModel), 又包含应用程序特定的数据模型,那么这种应用程序就被称为拥有一种分解模型设计。
如果一个数据模型必须被多个线程共享,而且由于阻塞,一致性或复杂度等原因无法实现一个线程安全的模型时,可以考虑使用分解模型设计。
其他形式的单线程子系统
可以创建一个专门的线程或一个单线程的Executor来实现。
将Future和newSingleThreadExecutor一起使用
不吝指正。