Java執行緒(七):Callable和Future
接著上一篇繼續併發包的學習,本篇說明的是Callable和Future,它倆很有意思的,一個產生結果,一個拿到結果。
Callable介面類似於Runnable,從名字就可以看出來了,但是Runnable不會返回結果,並且無法丟擲返回結果的異常,而Callable功能更強大一些,被執行緒執行後,可以返回值,這個返回值可以被Future拿到,也就是說,Future可以拿到非同步執行任務的返回值,下面來看一個簡單的例子:
public class CallableAndFuture {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
try {
Thread.sleep(5000 );// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
FutureTask實現了兩個介面,Runnable和Future,所以它既可以作為Runnable被執行緒執行,又可以作為Future得到Callable的返回值,那麼這個組合的使用有什麼好處呢?假設有一個很耗時的返回值需要計算,並且這個返回值不是立刻需要的話,那麼就可以使用這個組合,用另一個執行緒去計算返回值,而當前執行緒在使用這個返回值之前可以做其它的操作,等到需要這個返回值時,再通過Future得到,豈不美哉!這裡有一個Future模式的介紹:
下面來看另一種方式使用Callable和Future,通過ExecutorService的submit方法執行Callable,並返回Future,程式碼如下:
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
});
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
程式碼是不是簡化了很多,ExecutorService繼承自Executor,它的目的是為我們管理Thread物件,從而簡化併發程式設計,Executor使我們無需顯示的去管理執行緒的生命週期,是JDK 5之後啟動任務的首選方式。
執行多個帶返回值的任務,並取得多個返回值,程式碼如下:
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);
for(int i = 1; i < 5; i++) {
final int taskID = i;
cs.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return taskID;
}
});
}
// 可能做一些事情
for(int i = 1; i < 5; i++) {
try {
System.out.println(cs.take().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
其實也可以不使用CompletionService,可以先建立一個裝Future型別的集合,用Executor提交的任務返回值新增到集合中,最後遍歷集合取出資料,程式碼略。更新於2016-02-05,評論中就這個說法引發了討論,其實是我沒有講清楚,抱歉。這裡再闡述一下:提交到CompletionService中的Future是按照完成的順序排列的,這種做法中Future是按照新增的順序排列的。所以這兩種方式的區別就像評論中fishjam所描述的那樣。