為什么要是用Callable和Future
Runnable的局限性
Executor采用Runnable作為基本的表達形式,雖然Runnable的run方法能夠寫入日志,寫入文件,寫入數據庫等操作,但是它不能返回一個值,或者拋出一個受檢查的異常,有些需要返回值的需求就不能滿足了。
能夠取消
Executor中的任務有四個狀態:創建,提交,開始和完成。如果說有些任務執行時間比較長,希望能夠取消該任務,Executor中的任務在未開始前是可以取消的,如果已經開始了,只能通過中斷的方式來取消。如果使用Callable和Future的結合,可以使用Future的canel方法取消任務,這樣就方便多了。
一個例子:
import java.util.concurrent.*; public class Demo1 { public static void main(String args[]) throws Exception { ServiceTask task = new ServiceTask(); ExecutorService executor = Executors.newCachedThreadPool(); Future<Integer> result = executor.submit(task); executor.shutdown(); System.out.println("正在執行任務"); Thread.sleep(1000); System.out.println("task運行結果為:" + result.get()); } } class ServiceTask implements Callable<Integer>{ @Override public Integer call() throws Exception { Thread.sleep(2000); int result = 0; // 假設一個很龐大的計算 for(int i=1;i<100;i++){ for (int j=0;j<i;j++){ result +=j; } } return result; } }
看一下執行結果:

這個例子就是一個非常簡單的使用Callable和Futute的例子,ServiceTask類實現了Callable接口,並返回一個Integer類型的值。
Future<Integer> result = executor.submit(task);這行代碼就是構造一個Future。使用其get()方法就能得到最后的運行值。
好了看完這一個簡單的例子,那就來仔細了解一下它們。
了解Callable和Future
Callable
來看一下callable的代碼:
public abstract interface Callable<V> { public abstract V call() throws Exception; }
可以看出它是接口,提到接口就可以明白接口是靈活的,支持傳入泛型參數。這個沒什么,我們來重點介紹一下Future
Future
首先來看關於它的介紹
Future提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。計算完成后只能使用 get 方法來獲取結果,如有必要,計算完成前可以阻塞此方法。取消則由 cancel 方法來執行。還提供了其他方法,以確定任務是正常完成還是被取消了。
來看一下Future的代碼
public abstract interface Future<V> { public abstract boolean cancel(boolean paramBoolean); public abstract boolean isCancelled(); public abstract boolean isDone(); public abstract V get() throws InterruptedException, ExecutionException; public abstract V get(long paramLong, TimeUnit paramTimeUnit) throws InterruptedException, ExecutionException, TimeoutException; }
提供了五個方法
public abstract boolean cancel(boolean paramBoolean)
試圖取消任務的執行(注意是試圖),因為存在一些任務已完成、已取消或者因為某些原因無法取消的因素,存在着取消失敗的可能性。
當canel方法起作用時,有兩個情況:
1.任務未開始,則該任務將永遠不會運行;
2.任務處於執行狀態,paramBoolean表示是否采用中斷的方式中斷線程。
public abstract boolean isCancelled()
如果任務正常取消的,則返回true。
public abstract boolean isDone();
如果任務已完成,則返回 true。 可能由於正常終止、異常或取消而完成,在所有這些情況中,此方法都將返回 true。
(注意如果調用isCanle方法,那么isDone將始終返回true).
public abstract V get() throws InterruptedException, ExecutionException;
重點到了!這是Future獲取計算結果的方式之一,使用get方法。(注意這里返回的是Callable中的泛型)
get方法取決於任務的狀態(未開始,運行中,已完成),如果任務已經完成,那么get會立即返回或者拋出一個異常;
如果任務沒有完成,那么get將阻塞知道任務完成。如果任務拋出了異常,那么get會將該異常封裝成ExecutionException拋出。
public abstract V get(long paramLong, TimeUnit paramTimeUnit) throws InterruptedException, ExecutionException, TimeoutException;
如果需要在給定時間后獲取計算結果,可以使用這個方法,如果超過給定時間之后沒有得到計算結果,則拋出TimeoutException。(注意這里返回的是Callable中的泛型)
如何使用
來看代碼:
import java.util.concurrent.*; public class Demo1 { public static void main(String args[]) throws Exception { // 1.先實例化任務對象 ServiceTask task = new ServiceTask(); // 2.實例化Executor框架中的線程池 ExecutorService executor = Executors.newCachedThreadPool(); // 3.使用submit方法將任務提交(返回的是一個Future) Future<Integer> result = executor.submit(task); // 4.記得關閉線程池 executor.shutdown(); System.out.println("正在執行任務"); Thread.sleep(1000); // 5.打印最后的結果 System.out.println("task運行結果為:" + result.get()); } } /** * Callable的實現類 */ class ServiceTask implements Callable<Integer>{ @Override public Integer call() throws Exception { Thread.sleep(2000); int result = 0; // 假設一個很龐大的計算 for(int i=1;i<100;i++){ for (int j=0;j<i;j++){ result +=j; } } return result; } }
運行結果:

接下來我們來試一下定時取結果:
還是在原來的代碼上修改:
import java.util.concurrent.*; public class Demo1 { public static void main(String args[]) throws Exception { // 1.先實例化任務對象 ServiceTask task = new ServiceTask(); // 2.實例化Executor框架中的線程池 ExecutorService executor = Executors.newCachedThreadPool(); // 3.使用submit方法將任務提交(返回的是一個Future) Future<Integer> result = executor.submit(task); // 4.記得關閉線程池 executor.shutdown(); System.out.println("正在執行任務"); Thread.sleep(1000); // 5.設置定時一秒取結果 System.out.println("task運行結果為:" + result.get(1,TimeUnit.MILLISECONDS)); } } /** * Callable的實現類 */ class ServiceTask implements Callable<Integer>{ @Override public Integer call() throws Exception { //這里睡眠2秒 Thread.sleep(2000); int result = 0; // 假設一個很龐大的計算 for(int i=1;i<100;i++){ for (int j=0;j<i;j++){ result +=j; } } return result; } }
來提前猜想一下,首先設置了定時一秒之后取得結果,但是ServiceTask設置兩秒的睡眠時間,理應取結果失敗,看一下運行結果:

是的,如果在規定時間內無法取到結果,就會返回TimeoutException。
談談FutureTask
FutureTask是Future的實現類,它繼承了RunnableFuture,RunnableFuture實際上繼承了Runnable和Future接口。
來看一下使用如何FutureTask:
import java.util.concurrent.*; public class FutureCallDemo2 { public static void main(String args[])throws Exception{ // 1.先實例化任務對象 FutureTaskService task = new FutureTaskService(); // 2.實例化Executor框架中的線程池 ExecutorService excutor = Executors.newCachedThreadPool(); // 3.直接new一個FutureTask FutureTask<Long> result = new FutureTask<Long>(task); // 4.提交任務 excutor.submit(result); // 5.關閉線程池 excutor.shutdown(); System.out.println("主線程正在執行任務"); System.out.println("task運行結果為:" + result.get()); } } /** * 繼承Callable接口 */ class FutureTaskService implements Callable<Long> { @Override public Long call() throws Exception { Thread.sleep(3000); // 10的階乘 long sum = 1; for (int i = 1; i <= 10; i++) { sum = sum * i; } return sum; } }
用法的話其實差不多。
總結:
Future和Callable可以實現異構任務,但是有很多值得考慮的地方。
比如一個類使用了兩個任務,一個負責渲染頁面,一個負責下載圖像。
偽代碼如下:
//通過獲取圖像 List<ImageData>ImageDataList = future.get(); for(ImageData data:ImageDataList ){ //渲染頁面 renderPage(data); }
看似並行的執行任務,但是卻存在着問題。如果說下載圖像的速度遠小於渲染頁面的速度,那么最終的執行速度就和串行無異了。
所以只有當大量相互獨立且同構的任務可以進行並發處理時,才能體現出將任務分到多個任務中帶來的性能提升,考慮實際情況再選擇使用會帶來事半功倍的效果。
本文參考:
Java並發編程實戰
