1. 程式人生 > >執行緒執行者(四)執行者執行返回結果的任務

執行緒執行者(四)執行者執行返回結果的任務

宣告:本文是《 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章,執行緒執行者中的執行多個任務並處理所有結果指南