執行緒執行者(四)執行者執行返回結果的任務
宣告:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González 譯者:許巧輝 校對:方騰飛,葉磊
執行者執行返回結果的任務
Executor framework的一個優點是你可以併發執行返回結果的任務。Java併發API使用以下兩種介面來實現:
- Callable:此介面有一個call()方法。在這個方法中,你必須實現任務的(處理)邏輯。Callable介面是一個引數化的介面。意味著你必須表明call()方法返回的資料型別。
- Future:此介面有一些方法來保證Callable物件結果的獲取和管理它的狀態。
在這個指南中,你將學習如何實現返回結果的任務,並在執行者中執行它們。
準備工作…
這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。
如何做…
按以下步驟來實現的這個例子:
1.建立FactorialCalculator類,指定它實現Callable介面,並引數化為Integer型別。
public class FactorialCalculator implements Callable<Integer> {
2.宣告一個私有的,型別為Integer,名為number的屬性,用來儲存任務將要計算出的數。
private Integer number;
3.實現FactorialCalculator構造器,初始化這個屬性。
public FactorialCalculator(Integer number){ this.number=number; }
4.實現call()方法。這個方法將返回FactorialCalculator的number屬性的階乘。
@Override public Integer call() throws Exception {
5.首先,建立和初始化在這個方法中使用的區域性變數。
int result = 1;
6.如果數是1或0,則返回1。否則,計算這個數的階乘。出於教學目的,在兩次乘之間,令這個任務睡眠20毫秒。
if ((num==0)||(num==1)) { result=1; } else { for (int i=2; i<=number; i++) { result*=i; TimeUnit.MILLISECONDS.sleep(20); } }
7.操作結果的資訊寫入控制檯。
System.out.printf("%s: %d\n",Thread.currentThread().getName(),result);
8.返回操作結果。
return result;
9.實現這個示例的主類,建立Main類,實現main()方法。
public class Main { public static void main(String[] args) {
10.使用Executors類的newFixedThreadPool()方法建立ThreadPoolExecutor來執行任務。傳入引數2。
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(2);
11.建立Future<Integer>物件的數列。
List<Future<Integer>> resultList=new ArrayList<>();
12.建立Random類產生的隨機數。
Random random=new Random();
13.生成0到10之間的10個隨機數。
for (int i=0; i<10; i++){ Integer number= random.nextInt(10);
14.建立一個FactorialCaculator物件,傳入隨機數作為引數。
FactorialCalculator calculator=new FactorialCalculator(number);
15.呼叫執行者的submit()方法來提交FactorialCalculator任務給執行者。這個方法返回Future<Integer>物件來管理任務,並且最終獲取它的結果。
Future<Integer> result=executor.submit(calculator);
16.新增Future物件到之前建立的數列。
resultList.add(result); }
17.建立一個do迴圈來監控執行者的狀態。
do {
18.首先,寫入資訊到控制檯,表明使用執行者的getCompletedTaskNumber()方法獲得的已完成的任務數。
System.out.printf("Main: Number of Completed Tasks:%d\n",executor.getCompletedTaskCount());
19.然後,對於數列中的10個Future物件,使用isDone()方法,將資訊寫入(到控制檯)表明它們所管理的任務是否已經完成
for (int i=0; i<resultList.size(); i++) { Future<Integer> result=resultList.get(i); System.out.printf("Main: Task %d: %s\n",i,result.isDone()); }
20.令這個執行緒睡眠50毫秒
try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
21.如果執行者中的已完成任務數小於10,重複這個迴圈。
} while (executor.getCompletedTaskCount()<resultList.size());
22.將獲得的每個任務的結果寫入控制檯。對於每個Future物件,通過它的任務使用get()方法獲取返回的Integer物件。
System.out.printf("Main: Results\n"); for (int i=0; i<resultList.size(); i++) { Future<Integer> result=resultList.get(i); Integer number=null; try { number=result.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
23.然後,在控制檯列印這個數。
System.out.printf("Main: Task %d: %d\n",i,number); }
24.最後,呼叫執行者的shutdown()方法來結束這個執行者。
executor.shutdown();
它是如何工作的…
在這個指南中,你已經學習瞭如何使用Callable介面來啟動返回結果的併發任務。你已經使用FactorialCalculator類實現了Callable介面,並引數化為Integer型別作為結果型別。因此,Integer就作為call()方法的返回型別。
Main類是這個示例的另一個關鍵點。它使用submit()方法提交一個Callable物件給執行者執行。這個方法接收Callable物件引數,並且返回一個Future物件,你可以以這兩個目標來使用它:
- 你可以控制任務的狀態:你可以取消任務,檢查任務是否已經完成。基於這個目的,你已經使用isDone()方法來檢查任務是否已經完成。
- 你 可以獲取call()方法返回的結果。基於這個目的,你已經使用了get()方法。這個方法會等待,直到Callable物件完成call()方法的執 行,並且返回它的結果。如果執行緒在get()方法上等待結果時被中斷,它將丟擲InterruptedException異常。如果call()方法丟擲 異常,這個方法會丟擲ExecutionException異常。
不止這些…
當你呼叫Future物件的get()方法,並且這個物件控制的任務未完成,這個方法會阻塞直到任務完成。Future介面提供其他版本的get()方法:
- get(long timeout, TimeUnit unit):這個版本的get方法,如果任務的結果不可用,等待它在指定的時間內。如果時間超時,並且結果不可用,這個方法返回null值。 TimeUnit類是個列舉類,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
參見
- 在第4章,執行緒執行者中的建立執行緒執行者指南
- 在第4章,執行緒執行者中的執行多個任務並處理第一個結果指南
- 在第4章,執行緒執行者中的執行多個任務並處理所有結果指南