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

图形用户界面应用程序:

  • 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<T> implements Runnable, Future<T> {
	private final FutureTask<T> computation = new Computation();
	
	/**
	 * 计算任务
	 */
	private class Computation extends FutureTask<T>{
		public Computation() {
			super(new Callable<T>() {
				@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<T, V> implements RunnableFuture<T> {
   ...//实现RunnableFuture接口, T: doInBackground返回的结果类型, V: publish, process的参数类型
}
使用实例可以如下:
SwingWorker<String, Integer> worker 
	= new SwingWorker<String, Integer>(){
		@Override
		protected String doInBackground() throws Exception {
			System.out.println("任务开始");
			// do some computation
			publish(20);
			publish(30);
			//...
			publish(100);
			return "返回计算结果";
		}

		@Override
		protected void process(List<Integer> 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中的数据模型有TableModelTreeModel等,且都被封闭在事件线程中。

线程安全的数据模型:

  • 线程安全的数据模型必须在更新模版时产生事件,这样视图才能在数据发生变化后进行更新。

分解数据模型:

  • 如果程序中既包含用于表示的数据模型(如TableModel,TreeModel), 又包含应用程序特定的数据模型,那么这种应用程序就被称为拥有一种分解模型设计
  • 如果一个数据模型必须被多个线程共享,而且由于阻塞一致性复杂度等原因无法实现一个线程安全的模型时,可以考虑使用分解模型设计

其他形式的单线程子系统

  • 可以创建一个专门的线程或一个单线程的Executor来实现。
  • FuturenewSingleThreadExecutor一起使用

不吝指正。

转载于:https://my.oschina.net/indestiny/blog/215487

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值