Callable 和 Future 是比較有趣的一對組合。當我們需要獲取線程的執行結果時,就需要用到它們。Callable用于產生結果,Future用于獲取結果。
1. Callable
Callable 是一個接口,它只包含一個call()方法。Callable是一個返回結果并且可能拋出異常的任務。
為了便于理解,我們可以將Callable比作一個Runnable接口,而Callable的call()方法則類似于Runnable的run()方法。
Callable的源碼如下:
1
2
3
|
public interface Callable<V> { V call() throws Exception; } |
說明:從中我們可以看出Callable支持泛型。
2. Future
Future 是一個接口。它用于表示異步計算的結果。提供了檢查計算是否完成的方法,以等待計算的完成,并獲取計算的結果。
Future的源碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public interface Future<V> { // 試圖取消對此任務的執行。 boolean cancel( boolean mayInterruptIfRunning) // 如果在任務正常完成前將其取消,則返回 true。 boolean isCancelled() // 如果任務已完成,則返回 true。 boolean isDone() // 如有必要,等待計算完成,然后獲取其結果。 V get() throws InterruptedException, ExecutionException; // 如有必要,最多等待為使計算完成所給定的時間之后,獲取其結果(如果結果可用)。 V get( long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; } |
說明: Future用于表示異步計算的結果。它的實現類是FutureTask,在講解FutureTask之前,我們先看看Callable, Future, FutureTask它們之間的關系圖,如下:
說明:
(01) RunnableFuture是一個接口,它繼承了Runnable和Future這兩個接口。RunnableFuture的源碼如下:
1
2
3
|
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); } |
(02) FutureTask實現了RunnableFuture接口。所以,我們也說它實現了Future接口。
示例和源碼分析(基于JDK1.7.0_40)
我們先通過一個示例看看Callable和Future的基本用法,然后再分析示例的實現原理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutionException; class MyCallable implements Callable { @Override public Integer call() throws Exception { int sum = 0 ; // 執行任務 for ( int i= 0 ; i< 100 ; i++) sum += i; //return sum; return Integer.valueOf(sum); } } public class CallableTest1 { public static void main(String[] args) throws ExecutionException, InterruptedException{ //創建一個線程池 ExecutorService pool = Executors.newSingleThreadExecutor(); //創建有返回值的任務 Callable c1 = new MyCallable(); //執行任務并獲取Future對象 Future f1 = pool.submit(c1); // 輸出結果 System.out.println(f1.get()); //關閉線程池 pool.shutdown(); } } |
運行結果:
4950
結果說明:
在主線程main中,通過newSingleThreadExecutor()新建一個線程池。接著創建Callable對象c1,然后再通過pool.submit(c1)將c1提交到線程池中進行處理,并且將返回的結果保存到Future對象f1中。然后,我們通過f1.get()獲取Callable中保存的結果;最后通過pool.shutdown()關閉線程池。
1. submit()
submit()在java/util/concurrent/AbstractExecutorService.java中實現,它的源碼如下:
1
2
3
4
5
6
7
8
9
|
public <T> Future<T> submit(Callable<T> task) { if (task == null ) throw new NullPointerException(); // 創建一個RunnableFuture對象 RunnableFuture<T> ftask = newTaskFor(task); // 執行“任務ftask” execute(ftask); // 返回“ftask” return ftask; } |
說明:submit()通過newTaskFor(task)創建了RunnableFuture對象ftask。它的源碼如下:
1
2
3
4
|
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); } |
2. FutureTask的構造函數
FutureTask的構造函數如下:
1
2
3
4
5
6
7
8
|
public FutureTask(Callable<V> callable) { if (callable == null ) throw new NullPointerException(); // callable是一個Callable對象 this .callable = callable; // state記錄FutureTask的狀態 this .state = NEW; // ensure visibility of callable } |
3. FutureTask的run()方法
我們繼續回到submit()的源碼中。
在newTaskFor()新建一個ftask對象之后,會通過execute(ftask)執行該任務。此時ftask被當作一個Runnable對象進行執行,最終會調用到它的run()方法;ftask的run()方法在java/util/concurrent/FutureTask.java中實現,源碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject( this , runnerOffset, null , Thread.currentThread())) return ; try { // 將callable對象賦值給c。 Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { // 執行Callable的call()方法,并保存結果到result中。 result = c.call(); ran = true ; } catch (Throwable ex) { result = null ; ran = false ; setException(ex); } // 如果運行成功,則將result保存 if (ran) set(result); } } finally { runner = null ; // 設置“state狀態標記” int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } } |
說明:run()中會執行Callable對象的call()方法,并且最終將結果保存到result中,并通過set(result)將result保存。
之后調用FutureTask的get()方法,返回的就是通過set(result)保存的值。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。