一、Callable 與 Runnable
先說一下java.lang.Runnable吧,它是一個接口,在它裡面隻聲明了一個run()方法:
public interface Runnable {
public abstract void run();
}
由于run()方法傳回值為void類型,是以在執行完任務之後無法傳回任何結果。
Callable位于java.util.concurrent包下,它也是一個接口,在它裡面也隻聲明了一個方法,隻不過這個方法叫做call():
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;
}
可以看到,這是一個泛型接口,該接口聲明了一個名稱為call()的方法,同時這個方法可以有傳回值V,也可以抛出異常。call()方法傳回的類型就是傳遞進來的V類型。
那麼怎麼使用Callable呢?一般情況下是配合ExecutorService來使用的,在ExecutorService接口中聲明了若幹個submit方法的重載版本:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
第一個方法:submit送出一個實作Callable接口的任務,并且傳回封裝了異步計算結果的Future。
第二個方法:submit送出一個實作Runnable接口的任務,并且指定了在調用Future的get方法時傳回的result對象。
第三個方法:submit送出一個實作Runnable接口的任務,并且傳回封裝了異步計算結果的Future。
是以我們隻要建立好我們的線程對象(實作Callable接口或者Runnable接口),然後通過上面3個方法送出給線程池去執行即可。
二、Future
Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、擷取結果。必要時可以通過get方法擷取執行結果,該方法會阻塞直到任務傳回結果。
Future<V>接口是用來擷取異步計算結果的,說白了就是對具體的Runnable或者Callable對象任務執行的結果進行擷取(get()),取消(cancel()),判斷是否完成等操作。我們看看Future接口的源碼:
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;
}
在Future接口中聲明了5個方法,下面依次解釋每個方法的作用:
- cancel方法用來取消任務,如果取消任務成功則傳回true,如果取消任務失敗則傳回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設定true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定傳回false,即如果取消已經完成的任務會傳回false;如果任務正在執行,若mayInterruptIfRunning設定為true,則傳回true,若mayInterruptIfRunning設定為false,則傳回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定傳回true。
- isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則傳回 true。
- isDone方法表示任務是否已經完成,若任務完成,則傳回true;
- get()方法用來擷取執行結果,這個方法會産生阻塞,會一直等到任務執行完畢才傳回;
- get(long timeout, TimeUnit unit)用來擷取執行結果,如果在指定時間内,還沒擷取到結果,就直接傳回null。
也就是說Future提供了三種功能:
1)判斷任務是否完成;
2)能夠中斷任務;
3)能夠擷取任務執行結果。
因為Future隻是一個接口,是以是無法直接用來建立對象使用的,是以就有了下面的FutureTask。
三、FutureTask
我們先來看一下FutureTask的實作:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask類實作了RunnableFuture接口,我們看一下RunnableFuture接口的實作:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實作了RunnableFuture接口。是以它既可以作為Runnable被線程執行,又可以作為Future得到Callable的傳回值。
分析:
FutureTask除了實作了Future接口外還實作了Runnable接口,是以FutureTask也可以直接送出給Executor執行。 當然也可以調用線程直接執行(FutureTask.run())。接下來我們根據FutureTask.run()的執行時機來分析其所處的3種狀态:
(1)未啟動,FutureTask.run()方法還沒有被執行之前,FutureTask處于未啟動狀态,當建立一個FutureTask,而且沒有執行FutureTask.run()方法前,這個FutureTask也處于未啟動狀态。
(2)已啟動,FutureTask.run()被執行的過程中,FutureTask處于已啟動狀态。
(3)已完成,FutureTask.run()方法執行完正常結束,或者被取消或者抛出異常而結束,FutureTask都處于完成狀态。

下面我們再來看看FutureTask的方法執行示意圖(方法和Future接口基本是一樣的,這裡就不過多描述了)
(1)當FutureTask處于未啟動或已啟動狀态時,如果此時我們執行FutureTask.get()方法将導緻調用線程阻塞;當FutureTask處于已完成狀态時,執行FutureTask.get()方法将導緻調用線程立即傳回結果或者抛出異常。
(2)當FutureTask處于未啟動狀态時,執行FutureTask.cancel()方法将導緻此任務永遠不會執行。當FutureTask處于已啟動狀态時,執行cancel(true)方法将以中斷執行此任務線程的方式來試圖停止任務,如果任務取消成功,cancel(...)傳回true;但如果執行cancel(false)方法将不會對正在執行的任務線程産生影響(讓線程正常執行到完成),此時cancel(...)傳回false。當任務已經完成,執行cancel(...)方法将傳回false。
最後我們給出FutureTask的兩種構造函數:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
事實上,FutureTask是Future接口的一個唯一實作類。
四、使用示例
通過上面的介紹,我們對Callable,Future,FutureTask都有了比較清晰的了解了,那麼它們到底有什麼用呢?我們前面說過通過這樣的方式去建立線程的話,最大的好處就是能夠傳回結果,加入有這樣的場景,我們現在需要計算一個資料,而這個資料的計算比較耗時,而我們後面的程式也要用到這個資料結果,那麼這個時Callable豈不是最好的選擇?我們可以開設一個線程去執行計算,而主線程繼續做其他事,而後面需要使用到這個資料時,我們再使用Future擷取不就可以了嗎?下面我們就來編寫一個這樣的執行個體。
1、使用Callable+Future擷取執行結果
package com.demo.test;
import java.util.concurrent.Callable;
public class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子線程在進行計算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
}
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTest {
public static void main(String[] args) {
//建立線程池
ExecutorService executor = Executors.newCachedThreadPool();
//建立Callable對象任務
Task task = new Task();
//送出任務并擷取執行結果
Future<Integer> result = executor.submit(task);
//關閉線程池
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主線程在執行任務");
try {
if(result.get()!=null){
System.out.println("task運作結果"+result.get());
}else{
System.out.println("未擷取到結果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任務執行完畢");
}
}
運作結果:
子線程在進行計算
主線程在執行任務
task運作結果4950
所有任務執行完畢
2、使用Callable+FutureTask擷取執行結果
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class CallableTest1 {
public static void main(String[] args) {
//第一種方式
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
//第二種方式,注意這種方式和第一種方式效果是類似的,隻不過一個使用的是ExecutorService,一個使用的是Thread
// Task task = new Task();
// FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
// Thread thread = new Thread(futureTask);
// thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主線程在執行任務");
try {
if(futureTask.get()!=null){
System.out.println("task運作結果"+futureTask.get());
}else{
System.out.println("future.get()未擷取到結果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任務執行完畢");
}
}
運作結果同上。
補充:
實作Runnable接口和實作Callable接口的差別:
1、Runnable是自從java1.1就有了,而Callable是1.5之後才加上去的。
2、Callable規定的方法是call(),Runnable規定的方法是run()。
3、Callable的任務執行後可傳回值,而Runnable的任務是不能傳回值(是void)。
4、call方法可以抛出異常,run方法不可以。
5、運作Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果。通過Future對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果。
6、加入線程池運作,Runnable使用ExecutorService的execute方法,Callable使用submit方法。