1. 程式人生 > >執行器中控制任務完成

執行器中控制任務完成

Java 9併發程式設計指南 目錄

執行器中控制任務完成

Java API提供FutureTask類作為一種可撤銷非同步計算,此類實現了Runnable和Future介面並提供Future介面的基礎實現。通過使用Callable或Runnable(Runnable物件不返回結果,所以假若Future物件返回結果的話,就需要傳參)物件建立FutureTask類。此類提供取消執行以及獲得計算結果的方法,還提供名為done()的方法允許在執行器中的任務執行完成後再執行一些程式碼。此類用來進行後處理操作,例如生成報告,通過電子郵件傳送結果,或者釋放資源。當FutureTask物件正在控制的任務執行完成時,done()方法被FutureTask類內部呼叫。此方法在任務結果被設定並且其狀態變為isDone之後呼叫,而不考慮任務是否已經被正常地取消或者結束。

預設情況下,此方法為空,通過重寫FutureTask類和實現此方法改變特性。在本節中,學習如何重寫此方法在任務執行之後執行程式碼。

準備工作

本範例通過Eclipse開發工具實現。如果使用諸如NetBeans的開發工具,開啟並建立一個新的Java專案。

實現過程

通過如下步驟完成範例:

  1. 建立名為ExecutableTask的類,實現String類引數化的Callable介面:

    public class ExecutableTask implements Callable<String>{
    
  2. 定義名為name的私有String屬性,儲存任務名稱。實現getName()方法,返回屬性值:

    	private final String name;
    	public String getName() {
    		return name;
    	}
    
  3. 實現類建構函式,初始化任務名稱:

    	public ExecutableTask(String name){
    		this.name = name;
    	}
    
  4. 實現call()方法,設定任務隨機休眠一段時間,並返回任務名稱的資訊:

    	@Override
    	public String call() throws Exception {
    		try{
    			long duration = (long)(Math.random() * 10
    ); System.out.printf("%s : Waiting %d seconds for results.\n", this.name, duration); TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) {} return "Hello, World. I am " + name; }
  5. 實現名為ResultTask的類,繼承String類引數化的FutureTask類:

    public class ResultTask extends FutureTask<String>{
    
  6. 定義名為name的私有String屬性,儲存任務名稱:

    	private final String name;
    
  7. 實現類建構函式,需要接收Callable物件作為引數。呼叫父類的建構函式並使用任務接收到的屬性初始化名稱屬性值:

    	public ResultTask(ExecutableTask callable) {
    		super(callable);
    		this.name = callable.getName();
    	}
    
  8. 重寫done()方法,判斷isCancelled()方法的值,根據返回值,輸出不同的資訊到控制檯:

    	@Override
    	protected void done() {
    		if(isCancelled()) {
    			System.out.printf("%s : Has been canceled\n" , name);
    		} else {
    			System.out.printf("%s : Has finished\n", name);
    		}
    	}
    
  9. 實現範例的主方法,建立一個包含main()方法的Main類:

    public class Main {
    	public static void main(String[] args) {
    
  10. 使用Executors類的newCachedThreadPool()方法建立ExecutorService:

    		ExecutorService executor = Executors.newCachedThreadPool();
    
  11. 建立陣列儲存五個ResultTask物件:

    		ResultTask resultTasks[] = new ResultTask[5];
    
  12. 初始化ResultTask物件,對陣列中的每個元素,首先使用物件建立ExecutorTask,然後建立ResultTask。接下來使用submit()方法傳送ResultTask到執行器:

    		for(int i = 0 ; i < 5 ; i ++){
    			ExecutableTask executableTask = new ExecutableTask("Task " +i);
    			resultTasks[i] = new ResultTask(executableTask);
    			executor.submit(resultTasks[i]);
    		}
    
  13. 設定主執行緒休眠5秒:

    		try {
    			TimeUnit.SECONDS.sleep(5);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  14. 取消傳送到執行器的所有任務:

    		for (int i=0; i<resultTasks.length; i++) {
    			resultTasks[i].cancel(true);
    		}
    
  15. 使用ResultTask物件的get()方法,輸出未被取消的任務結果到控制檯:

    		for (int i = 0 ; i < resultTasks.length ; i ++){
    			try {
    				if(!resultTasks[i].isCancelled()) {
    					System.out.printf("%s\n", resultTasks[i].get());
    				}
    			} catch (InterruptedException | ExecutionException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    
  16. 使用shutdown()方法結束執行器:

    		executor.shutdown();
    	}
    }
    

工作原理

當正在被控制的任務完成其執行時,done()方法被FutureTask類呼叫。本範例中,實現了Callable物件、ExecutableTask類,以及FutureTask類的子類,用來控制ExecutableTask物件的執行。

done()方法在生成結果值且改變任務狀態為isDone之後被FutureTask類內部呼叫。你無法改變任務結果值或者改變其狀態,但是你能夠關閉被任務使用的資源,輸出日誌資訊,或者傳送通知。FutureTask類可以用來確保特殊的任務只能執行一次,因為呼叫其run()方法將只執行封裝的Runnable/Callable介面一次(並且可用的情況下,使用get能夠得到結果)。

更多關注

本章“執行器中執行返回結果的任務“小節。