java併發編程(八): 圖形用戶界面應用程序

圖形用戶界面應用程序:

  • Swing的數據結構不是線程安全的,所以必須將它們限制在事件線程中
  • 幾乎全部的GUI工具包(awt ,swing)都被實現爲單線程子系統

爲何GUI是單線程的

  • 當前的GUI框架經過一個事件分發線程(Event Dispatch Thread, EDT)來處理GUI事件。
  • 單線程的GUI框架經過線程封閉機制來實現線程安全性。

串行事件處理:

  • 避免任務執行的時間過長,致使GUI沒法響應其餘事件。

Swing中的線程封閉機制:

  • Swing組件及數據模型對象(TableModel, TreeModel)也經過線程封閉機制實現線程安全性。
  • Swing中也存在單線程規則之外的狀況:

      1. SwingUtilities.isEventDispatchThread, 判斷當前線程是不是事件線程。 java

      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: ide

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一塊兒使用

不吝指正。 工具

相關文章
相關標籤/搜索