(二)Future接口
Future接口和實現Future接口的FutureTask類用來表示異步計算的結果。
當我們把Runnable接口或Callable接口的實現類提交(submit)給ThreadPoolExecutor或ScheduledThreadPoolExecutor時,ThreadPoolExecutor或 ScheduledThreadPoolExecutor會向我們返回一個FutureTask對象。
Future接口下提供方法來檢查計算是否完成,等待其完成,並檢索計算結果。 結果只能在計算完成後使用方法get進行檢索,如有必要,阻塞,直到準備就緒。 取消由cancel方法執行,isCancelled方法用於檢測計算是否被取消,isDone方法用於檢測計算是否完成。 提供其他方法來確定任務是否正常完成或被取消。
下面是對應的API:
<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)
Future<> submit(Runnable task)
FutureTask詳解
FutureTask除了實現Future接口外,還實現了Runnable接口。因此,FutureTask可以交給Executor執行,也可以由調用線程直接執行FutureTask.run()。根據FutureTask.run()方法被執行的時機,FutureTask可以處於下面3種狀態。
- 未啓動。FutureTask.run()方法還沒有被執行之前,FutureTask處於未啓動狀態。當創建一個FutureTask,且沒有執行FutureTask.run()方法之前,這個FutureTask處於未啓動狀態。
- 已啓動。FutureTask.run()方法被執行的過程中,FutureTask處於已啓動狀態。
- 已完成。FutureTask.run()方法執行完後正常結束,或被取消FutureTask.cancel(…),或執行FutureTask.run()方法時拋出異常而異常結束,FutureTask處於已完成狀態。
當FutureTask處於未啓動或已啓動狀態時,執行FutureTask.get()方法將導致調用線程阻塞;當FutureTask處於已完成狀態時,執行FutureTask.get()方法將導致調用線程立即返回結果或拋出異常。
當FutureTask處於未啓動狀態時,執行FutureTask.cancel()方法將導致此任務永遠不會被執行;當FutureTask處於已啓動狀態時,執行FutureTask.cancel(true)方法將以中斷執行此任務線程的方式來試圖停止任務;當FutureTask處於已啓動狀態時,執行FutureTask.cancel(false)方法將不會對正在執行此任務的線程產生影響(讓正在執行的任務運行完成);當FutureTask處於已完成狀態時,執行FutureTask.cancel(…)方法將返回false。
FutureTask的使用
可以把FutureTask交給Executor執行;也可以通過ExecutorService.submit(...)方法返回一個FutureTask,然後執行FutureTask.get()方法或FutureTask.cancel(...)方法。除此以外,還可以單獨使用FutureTask
1、FutureTask的啓動:
(1)交給線程池的Execute或submit方法執行;
import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
class test{
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
//用FutureTask包裝Runnable或者Callable對象
FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
@Override
public String call() {
try{
String a = "return String";
return a;
}
catch(Exception e){
e.printStackTrace();
return "exception";
}
}
});
//交給線程池的Execute或submit方法執行
tpe.submit(future);
try{
System.out.println(future.get());
}
catch(Exception e){
e.printStackTrace();
}
finally{
tpe.shutdown();
}
}
}
(2)由調用線程直接執行:在調用線程中執行futureTask.run()方法;
把上述代碼的 tpe.submit(future); 替換爲 future.run();
當一個線程需要等待另一個線程把某個任務執行完後它才能繼續執行,此時可以使用FutureTask
。假設有多個線程執行若干任務,每個任務最多隻能被執行一次。當多個線程試圖同時執行同一個任務時,只允許一個線程執行任務,其他線程需要等待這個任務執行完後才能繼續執行。
private final ConcurrentMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();
private String executionTask(final String taskName)
throws ExecutionException, InterruptedException {
while (true) {
Future<String> future = taskCache.get(taskName); // 1.1, 2.1
if (future == null) {
Callable<String> task = new Callable<String>() {
@Override
public String call() throws InterruptedException {
return taskName;
}
};
FutureTask<String> futureTask = new FutureTask<String>(task);
future = taskCache.putIfAbsent(taskName, futureTask); // 1.3
if (future == null) {
future = futureTask;
futureTask.run(); // 1.4執行任務
}
}
try {
return future.get(); // 1.5, 2.2
} catch (CancellationException e) {
taskCache.remove(taskName, future);
}
}
}
2、FutureTask執行完後結果的獲取 :futureTask.get( )
(1)在已啓動的狀態調用futureTask.get( )或導致調用線程阻塞,知道FutureTask執行完畢,然後得到返回的FutureTask對象,調用futureTask.get( )獲得任務返回值;
(2)在已完成狀態調用futureTask.get( ),將導致調用線程立即返回(正常完成,得到FutureTask對象)或者拋出異常(被取消或者因異常而結束);
3、futureTask被取消:futureTask.cancel( )
(1)在未啓動狀態調用futureTask.cancel( )會導致該任務永遠不會再執行;
(2)在已啓動狀態:
調用futureTask.cancel(true )會以中斷的方式嘗試停止任務,如果該任務不響應中斷則無法停止;
調用futureTask.cancel(false)將不會對正在執行的線程產生影響,也就是已啓動的線程會讓他執行完畢;
(3)在已完成狀態調用futureTask.cancel( )則會返回false;
FutureTask使用場景
當一個線程需要等待另一個線程把某個任務執行完以後它才能繼續執行時;
有若干線程執行若干任務,每個任務最多隻能被執行一次;
當多個線程師徒執行同一個任務,但只能允許一個線程執行此任務,其它線程需要等這個任務被執行完畢以後才能繼續執行時;
(四)Runnable接口和 Callable接口
Runnable接口和Callable接口的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。
它們之間的區別是Runnable不會返回結果,而Callable可以返回結果。
除了可以自己創建實現Callable接口的對象外,還可以使用工廠類Executors來把一個Runnable包裝成一個Callable。
下面是Executors
提供的,把一個Runnable
包裝成一個Callable
的API。
public static Callable<Object> callable(Runnable task) // 假設返回對象Callable1
下面是Executors
提供的,把一個Runnable
和一個待返回的結果
包裝成一個Callable
的API。
public static <T> Callable<T> callable(Runnable task, T result) // 假設返回對象Callable2
前面講過,當我們把一個Callable對象(比如上面的Callable1或Callable2)提交給ThreadPoolExecutor或ScheduledThreadPoolExecutor執行時,submit(…)會向我們返回一個FutureTask對象。我們可以執行FutureTask.get()方法來 等待任務執行完成。當任務成功完成後FutureTask.get()將返回該任務的結果。
例如,如果提交的是對象Callable1,FutureTask.get()方法將返回null;如果提交的是對象Callable2,FutureTask.get()方法將返回result對象。
(五)ExecutorService
ExecutorService是一個接口,ExecutorService接口繼承了Executor接口,定義了一些生命週期的方法。
ExecutorService接口繼承自Executor接口,它提供了更豐富的實現多線程的方法,比如,ExecutorService提供了關閉自己的方法,以及可爲跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。 可以調用ExecutorService的shutdown()方法來平滑地關閉 ExecutorService,調用該方法後,將導致ExecutorService停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉ExecutorService。因此我們一般用該接口來實現和管理多線程。
ExecutorService的生命週期包括三種狀態:運行、關閉、終止。創建後便進入運行狀態,當調用了shutdown()方法時,便進入關閉狀態,此時意味着ExecutorService不再接受新的任務,但它還在執行已經提交了的任務,當素有已經提交了的任務執行完後,便到達終止狀態。如果不調用shutdown()方法,ExecutorService會一直處在運行狀態,不斷接收新的任務,執行新的任務,服務器端一般不需要關閉它,保持一直運行即可。