JAVA 線程池之Callable返回結果


本文介紹如何向線程池提交任務,並獲得任務的執行結果。然后模擬 線程池中的線程在執行任務的過程中拋出異常時,該如何處理。

 

一,執行具體任務的線程類

要想 獲得 線程的執行結果,需實現Callable接口。FactorialCalculator 計算 number的階乘,具體實現如下:

 1 import java.util.concurrent.Callable;
 2 import java.util.concurrent.TimeUnit;
 3 
 4 /**
 5  * Created by Administrator on 2017/9/26.
 6  */
 7 public class FactorialCalculator implements Callable<Integer> {
 8 
 9     private Integer number;
10 
11     public FactorialCalculator(Integer number) {
12         this.number = number;
13     }
14     public Integer call() throws Exception {
15         int result = 1;
16 
17         if (number == 0 || number == 1) {
18             result = 1;
19         }else {
20             for (int i = 2; i < number; i++) {
21                 result *= i;
22                 TimeUnit.MICROSECONDS.sleep(200);
23                 if (i == 5) {
24                     throw new IllegalArgumentException("excepion happend");//計算5以上的階乘都會拋出異常. 根據需要注釋該if語句
25                 }
26             }
27         }
28         System.out.printf("%s: %d\n", Thread.currentThread().getName(), result);
29         return result;
30     }
31 }

上面23行--25行的if語句表明:如果number大於5,那么 if(i==5)成立,會拋出異常。即模擬  執行5 以上的階乘時,會拋出異常。

 

二,提交任務的Main類

下面來看看,怎樣向線程池提交任務,並獲取任務的返回結果。我們一共向線程池中提交了10個任務,因此創建了一個ArrayList保存每個任務的執行結果。

第一行,首先創建一個線程池。第二行,創建List保存10個線程的執行結果 所要存入的地方,每個任務是計算階乘,因此線程的返回結果是 Integer。而這個結果只要計算出來了,是放在Future<Integer>里面。

第5-7行,隨機生成一個10以內的整數,然后創建一個 FactorialCalculator對象,該對象就是待執行的任務,然后在第8行 通過線程池的submit方法提交。

 1         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
 2         List<Future<Integer>> resultList = new ArrayList<Future<Integer>>();
 3         Random random = new Random();
 4         for (int i = 0; i < 10; i ++) {
 5             int rand = random.nextInt(10);
 6 
 7             FactorialCalculator factorialCalculator = new FactorialCalculator(rand);
 8             Future<Integer> res = executor.submit(factorialCalculator);//異步提交, non blocking.
 9             resultList.add(res);
10         }

需要注意的是:submit方法是個非阻塞方法參考這篇文章。提交了任務后,由線程池里面的線程負責執行該任務,執行完成后得到的結果最終會保存在 Future<Integer>里面,正如第8行所示。

As soon as we invoke the submit() method of ExecutorService the Callable are handed over to ExecutorService to execute.
Here one thing we have to note, the submit() is not blocking.
So, all of our Callables will be submitted right away to the ExecutorService, and ExecutorService will decide when to execute which callable.
For each Callable we get a Future object to get the result later.

 

接下來,我們在do循環中,檢查任務的狀態---是否執行完成。

 1         do {
 2 //            System.out.printf("number of completed tasks: %d\n", executor.getCompletedTaskCount());
 3             for (int i = 0; i < resultList.size(); i++) {
 4                 Future<Integer> result = resultList.get(i);
 5                 System.out.printf("Task %d : %s \n", i, result.isDone());
 6             }
 7             try {
 8                 TimeUnit.MILLISECONDS.sleep(50);
 9 
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13         } while (executor.getCompletedTaskCount() < resultList.size());

第3-6行for循環,從ArrayList中取出 每個 Future<Integer>,查看它的狀態 isDone() ,即:是否執行完畢。

第13行,while結束條件:當所有的線程的執行完畢時,就退出do循環。注意:當線程在執行過程中拋出異常了,也表示線程執行完畢。

 

獲取線程的執行結果

 1         System.out.println("Results as folloers:");
 2         for (int i = 0; i < resultList.size(); i++) {
 3             Future<Integer> result = resultList.get(i);
 4             Integer number = null;
 5 
 6             try {
 7                 number = result.get();// blocking method
 8             } catch (InterruptedException e) {
 9                 e.printStackTrace();
10             } catch (ExecutionException e) {
11                 e.printStackTrace();
12             }
13             System.out.printf("task: %d, result %d:\n", i, number);
14         }

第3行取出每個存儲結果的地方:Future<Integer>,第7行 從Future<Integer>中獲得任務的執行結果。Future.get方法是一個阻塞方法。但前面的do-while循環里面,我們已經檢查了任務的執行狀態是否完成,因此這里能夠很快地取出任務的執行結果。

We are invoking the get() method of Future to get the result. Here we have to remember that, the get() is a blocking method.

 

任務在執行過程中,若拋出異常,下面語句在獲取執行結果時會直接跳到catch(ExecutionException e)中。

 

number = result.get()

 

但它不影響Main類線程---主線程的執行。

 

整個Main類代碼如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by Administrator on 2017/9/26.
 */
public class Main {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        List<Future<Integer>> resultList = new ArrayList<Future<Integer>>();
        Random random = new Random();
        for (int i = 0; i < 10; i ++) {
            int rand = random.nextInt(10);

            FactorialCalculator factorialCalculator = new FactorialCalculator(rand);
            Future<Integer> res = executor.submit(factorialCalculator);//異步提交, non blocking.
            resultList.add(res);
        }

        // in loop check out the result is finished
        do {
//            System.out.printf("number of completed tasks: %d\n", executor.getCompletedTaskCount());
            for (int i = 0; i < resultList.size(); i++) {
                Future<Integer> result = resultList.get(i);
                System.out.printf("Task %d : %s \n", i, result.isDone());
            }
            try {
                TimeUnit.MILLISECONDS.sleep(50);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } while (executor.getCompletedTaskCount() < resultList.size());


        System.out.println("Results as folloers:");
        for (int i = 0; i < resultList.size(); i++) {
            Future<Integer> result = resultList.get(i);
            Integer number = null;

            try {
                number = result.get();// blocking method
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.printf("task: %d, result %d:\n", i, number);
        }
        executor.shutdown();
    }
}

在上面  number = result.get(); 語句中,我們 catch了兩個異常,一個是InterruptedException,另一個是ExecutionException。因為我們是在main線程內獲得任務的執行結果,main線程執行 result.get()會阻塞,如果在阻塞的過程中被(其他線程)中斷,則拋出InterruptedException。

若在任務的執行過程中拋出了異常(比如IllegalArgumentException),那么main線程在這里就會catch到ExecutionException。此時,就可以對拋出異常的任務“進行處理”。此外,線程池中執行該任務的線程,並不會因為 該任務在執行過程中拋出了異常而受到影響,該線程 可以繼續 接收並運行 下一次提交給它的任務。

 

圖中 task1、task2、task4 返回的結果為空,表明它們拋出了IllegalArgumentException異常。

而要想對異常進行處理,可參考:Java線程池異常處理最佳實踐 這篇文章

 

參考資料

《Java 7 Concurrency Cookbook》 chapter 4

 

原文:http://www.cnblogs.com/hapjin/p/7599189.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM