標籤:style blog color java os io for ar
AbstractExecutorService對ExecutorService的執行任務類型的方法提供了一個預設實現。這些方法包括submit,invokeAny和InvokeAll。
注意的是來自Executor介面的execute方法是未被實現,execute方法是整個體系的核心,所有的任務都是在這個方法裡被真正執行的,因此該方法的不同實現會帶來不同的執行策略。這個在後面分析ThreadPoolExecutor和ScheduledThreadPoolExecutor就能看出來。
首先來看submit方法,它的基本邏輯是這樣的:
1. 產生一個任務類型和Future介面的封裝介面RunnableFuture的對象
2. 執行任務
3. 返回future。
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
因為submit支援Callable和Runnable兩種類型的任務,因此newTaskFor方法有兩個重載方法:
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); } protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); }
上一篇文章裡曾經說過Callable和Runnable的區別在於前者帶傳回值,也就是說Callable=Runnable+傳回值。因此java中提供了一種adapter,把Runnable+傳回值轉換成Callable類型。這點可以在newTaskFor中的FutureTask類型的建構函式的代碼中看到:
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); sync = new Sync(callable); } public FutureTask(Runnable runnable, V result) { sync = new Sync(Executors.callable(runnable, result)); }
以下是Executors.callable方法的代碼:
public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); }
那麼RunnableAdapter的代碼就很好理解了,它是一個Callable的實現,call方法的實現就是執行Runnable的run方法,然後返回那個value。
static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
接下來先說說較為簡單的invokeAll:
1. 為每個task調用newTaskFor方法產生得到一個既是Task也是Future的封裝類對象的List
2. 迴圈調用execute執行每個任務
3. 再次迴圈調用每個Future的get方法等待每個task執行完成
4. 最後返回Future的list。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { if (tasks == null || unit == null) throw new NullPointerException(); long nanos = unit.toNanos(timeout); List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size()); boolean done = false; try { // 為每個task產生封裝對象 for (Callable<T> t : tasks) futures.add(newTaskFor(t)); long lastTime = System.nanoTime(); // 迴圈調用execute執行每個方法 // 這裡因為設定了逾時時間,所以每次執行完成後 // 檢查是否逾時,逾時了就直接返回future集合 Iterator<Future<T>> it = futures.iterator(); while (it.hasNext()) { execute((Runnable)(it.next())); long now = System.nanoTime(); nanos -= now - lastTime; lastTime = now; if (nanos <= 0) return futures; } // 等待每個任務執行完成 for (Future<T> f : futures) { if (!f.isDone()) { if (nanos <= 0) return futures; try { f.get(nanos, TimeUnit.NANOSECONDS); } catch (CancellationException ignore) { } catch (ExecutionException ignore) { } catch (TimeoutException toe) { return futures; } long now = System.nanoTime(); nanos -= now - lastTime; lastTime = now; } } done = true; return futures; } finally { if (!done) for (Future<T> f : futures) f.cancel(true); } }
最後說說invokeAny,它的痛點在於只要一個任務執行成功就要返回,並且會取消其他任務,也就是說重點在於找到第一個執行成功的任務。
這裡我想到了BlockingQueue,當所有的任務被提交後,任務執行返回的Future會被依次添加到一個BlockingQueue中,然後找到第一個執行成功任務的方法就是從BlockingQueue取出第一個元素,這個就是doInvokeAny方法用到的ExecutorCompletionService的基本原理。
因為兩個invokeAny方法都是調用doInvokeAny方法,下面是doInvokeAny的程式碼分析:
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { if (tasks == null) throw new NullPointerException(); int ntasks = tasks.size(); if (ntasks == 0) throw new IllegalArgumentException(); List<Future<T>> futures= new ArrayList<Future<T>>(ntasks); // ExecutorCompletionService負責執行任務,後面調用用poll返回第一個執行結果 ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); // 這裡出於效率的考慮,每次提交一個任務之後,就檢查一下有沒有執行完成的任務 try { ExecutionException ee = null; long lastTime = timed ? System.nanoTime() : 0; Iterator<? extends Callable<T>> it = tasks.iterator(); // 先提交一個任務 futures.add(ecs.submit(it.next())); --ntasks; int active = 1; for (;;) { // 嘗試擷取有沒有執行結果(這個結果是立刻返回的) Future<T> f = ecs.poll(); // 沒有執行結果 if (f == null) { // 如果還有任務沒有被提交執行的,就再提交一個任務 if (ntasks > 0) { --ntasks; futures.add(ecs.submit(it.next())); ++active; } // 沒有任務在執行了,而且沒有拿到一個成功的結果。 else if (active == 0) break; // 如果設定了逾時情況 else if (timed) { // 等待執行結果直到有結果或者逾時 f = ecs.poll(nanos, TimeUnit.NANOSECONDS); if (f == null) throw new TimeoutException(); // 這裡的更新不可少,因為這個Future可能是執行失敗的情況,那麼還需要再次等待下一個結果,逾時的設定還是需要用到。 long now = System.nanoTime(); nanos -= now - lastTime; lastTime = now; } // 沒有設定逾時,並且所有任務都被提交了,則一直等到第一個執行結果出來 else f = ecs.take(); } // 有返回結果了,嘗試從future中擷取結果,如果失敗了,那麼需要接著等待下一個執行結果 if (f != null) { --active; try { return f.get(); } catch (ExecutionException eex) { ee = eex; } catch (RuntimeException rex) { ee = new ExecutionException(rex); } } } // ExecutorCompletionService執行時發生錯誤返回了全是null的future if (ee == null) ee = new ExecutionException(); throw ee; } finally { // 嘗試取消所有的任務(對於已經完成的任務沒有影響) for (Future<T> f : futures) f.cancel(true); } }
後面接著分析ThreadPoolExecutor和ScheduledThreadPoolExecutor。