Google Code Prettify

2015年8月15日 星期六

java.util.concurrent - Future & Callable

在 Java 5 之前,寫多執行緒程式,唯一的辦法就是使用 Thread 類別及 Runnable 介面,Java 5 之後新增加 java.util.concurrent 這個 package,提供了豐富的類別、介面,在這之後 Java 的多執行緒程式設計才真的趨於完整,這篇先介紹 Callable 和 Future,類別圖如下。

在看程式之前,先對上圖說明如下:
  • Callable 介面只定義了一個 call() method,實作這個介面的好處是當執行緒執行結束後,可以傳回值,且值的型別可以由我們自定。 
  • FutureTask 並非 Future 唯一實作的類別,之後還會介紹別的實作 Future 介面的類別,在這篇我先介紹 FutureTask。
接下來看範例程式,這個程式會算出費式數列,傳入的參數是指出程式要列出幾個值,例如傳入 10,則列出 0 1 1 2 3 5 8 13 21 34 共 10 個值。
 1 package idv.steven.concurrency;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.Callable;
 6 import java.util.concurrent.ExecutionException;
 7 import java.util.concurrent.FutureTask;
 8 
 9 public class FibonacciDemo implements Callable<List<Long>> {
10     private long number;
11     
12     public FibonacciDemo(long number) {
13         this.number = number;
14     }
15 
16     @Override
17     public List<Long> call() throws Exception {
18         List<Long> fib = new ArrayList<Long>();
19         fib.add(0L);
20         fib.add(1L);
21         for(int i=2; i<number; i++) {
22             Long f = fib.get(fib.size()-1) + fib.get(fib.size()-2);
23             fib.add(f);
24         }
25         
26         return fib;
27     }
28 
29     public static void main(String[] args) {
30         Callable<List<Long>> fibonacci = new FibonacciDemo(10);
31         FutureTask<List<Long>> fibonacciTask = new FutureTask<List<Long>>(fibonacci);
32         
33         Thread t = new Thread(fibonacciTask);
34         t.start();
35         
36         try {
37             t.sleep(10);
38             
39             boolean canceled = fibonacciTask.cancel(false);
40             System.out.println("canceled = " + canceled);
41             
42             if (!fibonacciTask.isCancelled()) {
43                 List<Long> fib = fibonacciTask.get();
44                 for(Long f:fib) {
45                     System.out.print(f + " ");
46                 }
47             }
48             
49 //            if (fibonacciTask.isDone()) {
50 //                List<Long> fib = fibonacciTask.get();
51 //                for(Long f:fib) {
52 //                    System.out.print(f + " ");
53 //                }
54 //            }
55 //            else {
56 //                System.out.println("unfinished");
57 //            }
58         }
59         catch (InterruptedException | ExecutionException  e) {
60             e.printStackTrace();
61         }
62     }
63 }




程式說明如下:

  • FutureTask 有兩個建構式,一個接受實作 Callable 的類別,另一個接受實作 Runnable 的類別,這個程式實作了 Callable (line 9),並指定傳回的值為 List<Long>,也就是所有計算所得的數字。大多數的人會選擇實作 Callable,因為實作 Runnable 的話,還要傳入一個變數,用來儲存傳回值。
  • FutureTask 類別是一個實作 Runnable 介面的類別,要建立執行緒,仍需透過 Thread 類別,所以可以看到第 31 行傳入我們實作 Callable 的類別物件給 FutureTask 後,為了建立一個執行緒,第 33 行再將 FutureTask 類別的物件傳給 Thread,然後在第 34 行啟動一個新的執行緒。
  • 第 17~27 行實作 call() method,計算費式數列後,傳回給主程式。
  • 第 39~47 行及第 49~57 行是不同兩個版本,執行出來的結果是一樣的。
  • 要如何取得 call() method 傳回的值呢? 使用 FutureTask 的 get() method ! 就算是實作 Runnable 介面,也是透過 get() 取得傳回值。(line 43、50)
  • 先說明第一個版本,第 39 行是什麼意思呢? cancel() method 是試著強制中斷 FutureTask 的執行緒! 傳入的參數 true 表示,不管這個執行緒處於什麼狀態,都將它中斷結束,如果傳入的是 false,則是當執行緒已經進入 call() method 且還沒執行完離開 call() method 則不要中斷,否則都中斷。至於傳回值即是 true 表示程式沒有執行完就被中斷,false 則是有執行完。這也是為什麼會有第 37 行睡了 10 毫秒的原因,如果主程式不略微停頓一下,讓 FutureTask 的那個執行緒計算一下費式數列,第 39 行有可能傳回 true,也就是程式被中斷了!
  • 第 42 行判斷是否有被中斷,如果沒有,就是有順利的計算完傳回值,所以第 43 行呼叫 get() 取得傳回值,接下來的 for 迴圈當然就是印出結果。
  • 再來說明第二個版本,同樣的要保留第 37 行,讓費式數列有時間被計算,第 49 行和第 42 行剛好相反,它會判斷執行緒是否有被執行完? 有的話就傳回 true,沒有就傳回 false。傳回 true 的話,第 50 行取得計算結果然後印出來。
Callable 這種會傳回值的架構,在有一個計算很耗時,還蠻好用的,可以先產生一個 Thread 去計算這個值,然後做點別的事,之後再回來取得計算結果。當然,即使用只用 Thread、Runnable 也可以做到同樣的功能,只是會比較麻煩,現在 Java 直接提供相關的 API。

沒有留言:

張貼留言