建立一個Thread Pool Executor
建立一個ThreadPoolExecutor是很簡單的。你只需調用CustomThreadPoolExecutor類構造器並傳輸恰當的配置參數。以下程式碼片段是通過定義核心線程數和線程最大數的相同值來建立一個固定尺寸的線程池:
private ThreadPoolExecutor executor; public OrderProcessorMain() { // create a thread pool with fixed number of threads executor = new CustomThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); .. } |
0L值確保背景工作執行緒不因休止狀態而中斷。最後一個參數是攔截列隊對象,為了在任務執行之前控制任務。這個列隊只控制execute 方法提交的Runnable任務。
或者,可以使用java.uti.concurrent.Executors 功用類建立一個固定的線程池。
ExecutorService executorService = Executors.newFixedThreadPool(2);
這個方法的執行恢複了帶有一個固定核心和最大線程池的ThreadPoolExecutor對象。
建立並提交任務
建立ThreadPoolExecutor之後,你需要建立任務來處理訂單。你通過使用在前一節建立的 OrderProcessorCallable 類來建立這些任務。OrderProcessorCallable構造器採用一個任務名稱和一個訂單列隊對象來檢索OrderVO對象。
// create Callable task OrderProcessorCallable callable1 = new OrderProcessorCallable( "OrderProcessor_1", orderVOQueue); |
記住OrderProcessorCallable類的call方法不會返回直到running變數是正確的或者代碼拋出一個異常。
下一步是給callable對象儲存一個reference。這個讓你能夠調用setRunning方法來更新running變數值並且使 call方法平靜的返回。這個技術的一個優點就是你可以調用其他方法來得到對象狀態資訊,例如,在一定的時間點已經處理的訂單數量。
// store reference to callable object in collection callableMap.put(callable1.getThreadName(), callable1); |
以上代碼非常有用因為通過ExecutorService.submit方法返回的Future對象不能用於得到Callable對象的reference或是調用Callable對象的任何方法。
為了執行OrderProcessorCallable任務,你調用submit方法並傳輸一個任務參考。這個方法返回一個type Future對象,你可以用於以下的目的:
檢查任務狀態
得到一個通過Callable.call()方法返回的結果對象
取消任務
// submit callable tasks Future future; future = executor.submit(callable1); futurList.add(future); |
Future對象也儲存在另一個集合中用來檢查任務狀態並檢索處理結果。如果你不想在一個集合中明確的儲存Future對象,你可以使用ExecutorCompletionService 功用類。它為從內部任務列隊中檢索和刪除已完成的任務提供了一個有用的方法。
保持任務進度跟蹤
為了檢查被一個任務處理的訂單數量,你可以使用在集合中儲存的OrderProcessorCallable對象參考。以下的程式碼片段是每間隔1000ms就列印出任務狀態直到orderVOQueue清空為止:
private void printProcessorStatus() throws InterruptedException { // print processor status until all orders are processed while (!orderVOQueue.isEmpty()) { for (Map.Entry e : callableMap .entrySet()) { Logger.log(e.getKey() + " processed order count: " + e.getValue().getProcessedCount()); } Thread.sleep(1000); } } |
關閉ThreadPoolExecutor和任務
ExecutorService.shutdown()可用於關閉執行器。當你調用shutdown()的時候,執行器啟動一個有序關閉所有以前提交的任務,而且你可能不會在提交新任務。以下的代碼調用shutdown()方法以避免新任務被提交。此後,它更新orderCallable的運行狀態false, 這將導致call方法返回。
// shutdown() method will mark the thread pool shutdown to true executor.shutdown(); // mark order processor callable to return for (Map.Entry orderProcessor : callableMap.entrySet()) { orderProcessor.getValue().setRunning(false); } |
迫使ThreadPoolExecutor和任務關閉
你可以調用ExecutorService.shutdownNow()方法來迫使執行器去關閉。同樣,在調用這個方法之後,你不在提交新任務。它停止正在等待的任務處理,並通過發出一個中斷試圖停止正在啟動並執行任務。ExecutorService.shutdownNow()也恢複正在等待被執行的工作清單。
List notExecutedTasks = executor.shutdownNow();
你可以通過調用Future.cancel(boolean mayInterruptIfRunning)方法取消個別任務。True值是指為一個已經執行的任務所發出的一個中斷。如果任務沒有開始,它不會運行。你可以通過使用Future.isCancelled()檢查一個任務取消狀態,其中,恢複true,如果任務在它正常完成之前被取消。
異常處理和任務結果
你可以使用Future.get()或Future.get(long timeout, TimeUnit unit)方法來檢索任務結果。no-argument方法會阻礙其他的方法直到任務通過正常的執行,取消或是拋出的一個異常而完成。要謹慎的使用這個方法,因為它會無限期的等待一個任務的完成。帶有timeout參數的Future.get()方法更有用,因為它會讓一個方法在特定時間內返回:
for (Future f : futurList) { try { Integer result = f.get(1000, TimeUnit.MILLISECONDS); Logger.log(f + " result. Processed orders " + result); } catch (InterruptedException e) { Logger.error(e.getMessage(), e); } catch (ExecutionException e) { Logger.error(e.getCause().getMessage(), e); } catch (TimeoutException e) { Logger.error(e.getMessage(), e); } catch (CancellationException e) { Logger.error(e.getMessage(), e); } // to avoid printing completed tasks, you may want to remove // the completed task from futureList here } |
Future.get()方法拋出不同的異常來為每一個任務的失敗給出一個確切的原因:
InterruptedException被拋出——如果一個線程在等待計算結果的時候被中斷。
TimeOutException被拋出——如果一個結果沒有在特定的時間內被檢索。
CancellationException被拋出——如果計算被取消。
ExecutionException被拋出——如果一個任務計算失敗通過拋出任何異常,包括運行異常。這確保了執行線程不會因在任務執行中的一個異常而被終止。這個行為的副作用就是在你想要產生一個任務運行異常的應用程式方案中,你需要從ExecutionException中檢索運行異常並再次將它拋出。
總結
OrderProcessorMain類(參見 Listing 3)使用了在先前章節中討論的所有內容。特別是,main()方法執行下列步驟:
建立並配置CustomThreadPoolExecutor對象。
建立並提交OrderProcessorCallable任務。
將OrderVO對象放入order-blocking列隊。
定期列印處理器狀態直到order-blocking列隊為空白。
關閉線程池處理器。
任務完成後列印任務結果。
要查看示正在啟動並執行應用程式範例,只要運行OrderProcessorMain.main(String[] args)方法。為了方便起見,提供給本文的原始碼下載(source code download)可以作為一個Eclipse項目輸入。