通常Java線程池執行的任務有兩種類型,一種是不帶返回值的Runnable, 另一種是帶返回值的Callable。
對於不帶返回值的任務通常我們不太關注任務是否執行結束以及結束後應該做做些什麼,我們將任務提交給線程池, 然後顧自己幹別的事情。
帶返回值的任務執行結果通常受到當前任務的依賴,任務提交給線程池後還需要等待任務的返回。對於任務結果我們會有不同的需求,有時候當前任務依賴所有提交給線程池的任務的結果, 而有時候有隻依賴某一個任務的執行結果,就好比飯店的服務員需要等待寶箱中所有顧客用餐完畢纔來收拾,而食堂的阿姨卻可以單個學生用餐完畢而來收拾。
Java線程池對對於這兩種需求提供不同的解決方案
對於依賴所有任務執行結果的可以直接使用線程池的invokeAll方法
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<Callable<Integer>> tasks = new ArrayList<>();
for( int i = 0; i < 10; i++) {
tasks.add(()->{
Random random = new Random();
int second = random.nextInt(10);
Thread.sleep(second * 1000) ;
return second;
});
}
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Integer>> futures = executorService.invokeAll(tasks);
for( int i = 0; i < futures.size(); i++) {
System.out.println(futures.get(i).get());
}
executorService.shutdown();
}
}
以上程序清單中的線程池執行10個任務,這些任務會做隨機延時,所有的任務都放在tasks變量中。
我們初始化一個長度爲時的固定大小的線程池執行這些任務,方法invokeAll調用會阻塞,在所有任務執行完畢後返回,然後程序打印這些返回結果。我們運行這段代碼會卡斷很長時間,接着瞬間出結果, 這是invokeAll的特性:所欲任務必須執行完畢後才返回。
對於不依賴所有任務的執行結果,而可以單獨處理每個任務結果的,invokeAll就顯得不友好了,雖然最終結果沒區別,執行完所有任務都需要話同樣的時間,可是執行完一個任務就處理一個任務的結果不是顯得更加人性化麼,比如加載多張網絡圖片,加載完成一張就顯示一張顯然有更好的用戶體驗,對於這種需求我們可以使用CompletionService。
CompletionService能逐個返回任務的執行結果,誰先執行完畢返回誰。 它利用了阻塞隊列的特想,當它察覺到有任務執行完畢時則將執行的結果,一個Future放入它維護的一個無界阻塞隊列,外部程序就可以通過take方法拿取,如果阻塞隊列爲空,也就是還沒有執行完畢的任務, 那麼take方法則阻塞,外部程序繼續等待。
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<Callable<Integer>> tasks = new ArrayList<>();
for( int i = 0; i < 10; i++) {
tasks.add(()->{
Random random = new Random();
int second = random.nextInt(10);
Thread.sleep(second * 1000) ;
return second;
});
}
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService(executorService);
tasks.forEach(task -> completionService.submit(task));
for( int i = 0; i < tasks.size(); i++) {
System.out.println(completionService.take().get());
}
executorService.shutdown();
}
}
執行上面的代碼不會長時間卡斷後瞬間出結果,它會平緩的打印每個任務的執行結果, 直到所有任務執行完畢而結束程序。