java图形界面应用程序_java并发编程(八): 图形用户界面应用程序

本文探讨了Java图形用户界面(GUI)的线程安全问题,重点在于Swing组件的线程封闭机制。内容包括事件分发线程(EDT)的重要性,如何在Swing中安全地更新组件,以及使用SwingUtilities的方法进行线程调度。同时,文章还介绍了SwingWorker在处理长时间任务和提供进度反馈中的应用。
摘要由CSDN通过智能技术生成

图形用户界面应用程序:

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任务:

短时间任务可以在事件线程中执行,长时间任务则放在另一个线程中执行。

模型对象与数据对象的控制流。

697abf210ce96752bff95240b786d3b4.gif

长时间的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一起使用

不吝指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值