在Java多線程之中,Callable
和Future
的使用時非常廣泛的。在之前的文章中,我們瞭解了關於Java線程池基礎的一些內容,知道如何提交Runnable
的任務。但是,Runnable
的任務是無法有返回值,也不能拋出異常的。而有些時候,我們希望一個線程能夠有一些返回值。在Java 5中,引入了java.util.concurrent.Callable
接口,這個接口很類似於Runnable
接口,但是可以返回一個對象,或者拋出異常。
Java Callable
Java的Callable
接口使用了泛型來定義返回的對象的類型。Executors
類提供了一些很實用的方法來在線程池中執行Callable
的任務。因爲Callable
的任務通過並行的方式來運行,所以我們需要等待返回的對象。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Java Future
Java的Callable
對象返回的就是java.util.concurrent.Future
對象。通過使用Java Future
對象,我們可以知道Callable
任務的執行狀態,並且獲得返回的對象。Future
接口提供get()
方法來讓開發者可以等待Callable
任務的執行,然後獲得對應的結果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Java Future提供了一個cancel()
方法來取消關聯的Callable
任務的執行。其中的get()
方法是包含一個重載的方法的,我們可以指定等待的時間,而不需要無限期的等待Callable
任務的執行。這個方法可以有效的防止一個線程的無限期的阻塞。
Future
也提供一個isDone()
和一個isCancelled()
方法來找到其關聯的Callable
任務的執行狀態。
下面是使用Callable
的例子,是在一秒之後返回執行任務的名字。我們通過使用Executor
框架來並行執行100個任務,然後用Future
來獲得任務的執行結果。
package com.sapphire.threads;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
//return the thread name executing this callable task
return Thread.currentThread().getName();
}
public static void main(String args[]){
//Get ExecutorService from Executors utility class, thread pool size is 10
ExecutorService executor = Executors.newFixedThreadPool(10);
//create a list to hold the Future object associated with Callable
List<Future<String>> list = new ArrayList<Future<String>>();
//Create MyCallable instance
Callable<String> callable = new MyCallable();
for(int i=0; i< 100; i++){
//submit Callable tasks to be executed by thread pool
Future<String> future = executor.submit(callable);
//add Future to the list, we can get return value using Future
list.add(future);
}
for(Future<String> fut : list){
try {
//print the return value of Future, notice the output delay in console
// because Future.get() waits for task to get completed
System.out.println(new Date()+ "::"+fut.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
//shut down the executor service now
executor.shutdown();
}
}
當我們運行上面程序的時候,我們的輸出會有延遲,因爲Future的get()
方法會一直等待Callable
的任務執行完畢。同時需要注意的是,線程池中,我們僅僅會有10個線程來處理之前定義的Callable
任務。
下面是上面程序的輸出結果:
Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
...
當我們想要覆蓋掉
Future
接口的一些行爲的時候,舉例來說,假設我們需要覆蓋其中的get()
方法來做一些超時處理而不進行持續等待等操作的時候。Java中的FutureTask
類在這種時候就會非常有用了,它是Future
接口的實現類。
FutureTask
在上面,我們瞭解到使用Callable
以及Future
接口來處理多線程的一些便利之處。
而FutureTask
是Future
接口的一個基礎實現,並且提供了異步處理的功能,FutureTask
包含了一些方法來啓動或者取消任務,也包含一些方法來返回Future
的狀態,來確認Future
是完成了還是去掉了。我們需要一個Callable
對象來創建一個FutureTask
然後,我們可以通過ThreadPoolExecutor
來異步處理這些任務。
下面是FutureTask
的代碼舉例,因爲FutureTask
是需要Callable
的,所以我們來創建一個Callable
的實現:
package com.sapphire.threads;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private long waitTime;
public MyCallable(int timeInMillis){
this.waitTime=timeInMillis;
}
@Override
public String call() throws Exception {
Thread.sleep(waitTime);
//return the thread name executing this callable task
return Thread.currentThread().getName();
}
}
下面是一個FutureTask
方法的例子,下面展示的是關於使用FutureTask
方法的一些舉例:
package com.sapphire.threads;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureTaskExample {
public static void main(String[] args) {
MyCallable callable1 = new MyCallable(1000);
MyCallable callable2 = new MyCallable(2000);
FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(futureTask1);
executor.execute(futureTask2);
while (true) {
try {
if(futureTask1.isDone() && futureTask2.isDone()){
System.out.println("Done");
//shut down executor service
executor.shutdown();
return;
}
if(!futureTask1.isDone()){
//wait indefinitely for future task to complete
System.out.println(
"FutureTask1 output="+futureTask1.get());
}
System.out.println("Waiting for FutureTask2 to complete");
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if(s !=null){
System.out.println("FutureTask2 output="+s);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}catch(TimeoutException e){
//do nothing
}
}
}
}
當我們運行上面的程序,你會發現,有一段時間是不會輸出任何東西到控制檯的,因爲FutureTask
的get()
方法會等待任務的完成,然後纔會返回輸出的對象。在FutureTask
中也有一個重載的方法會等待指定的時間。需要注意的是,當調用isDone()
方法來確定程序一旦結束,任務也會完成。
輸出如下:
FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=pool-1-thread-2
Done
從上面的例子來說是沒有使用到FutureTask
的便利之處的,但是當我們想要覆蓋掉Future
接口方法的實現,而不像實現Future
接口的每一個方法的時候,我們就可以考慮使用FutureTask
。