說一下 Future 和 FutureTask,以及他們之前的區別

1.簡介:

線程的創建方式中有兩種,一種是實現Runnable接口另一種是繼承Thread,但是這兩種方式都有個缺點,那就是在任務執行完成之後無法獲取返回結果,於是就有了Callable接口,Future接口與FutureTask類的配和取得返回的結果.

先回顧一下java.lang.Runnable接口,就聲明瞭run(),其返回值爲void,當然就無法獲取結果

public interface Runnable {  
    public abstract void run();  
}

Callable的接口定義如下:該接口聲明瞭一個名稱爲call()的方法,同時這個方法可以有返回值V,也可以拋出異常

public interface Callable<V> {   
    V   call()   throws Exception;   
}

1.1.線程池解決:Callable

無論是Runnable接口的實現類還是Callable接口的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都實現了ExcutorService接口,而因此Callable需要和Executor框架中的ExcutorService結合使用,我們先看看ExecutorService提供的方法:

//ExcutorService接口的方法
<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個方法提交給線程池去執行即可。
在這裏插入圖片描述


1.2. 工廠類Executors解決:Callable

還有點要注意的是,除了我們自己實現Callable對象外,我們還可以使用工廠類Executors來把一個Runnable對象包裝成Callable對象。Executors工廠類提供的方法如下:

//工廠類Executors中的 方法
    public static Callable<Object> callable(Runnable task)  
    public static <T> Callable<T> callable(Runnable task, T result)

在這裏插入圖片描述


總結:其實上面兩種解決都是利用線程池去解決,因爲工廠類Executors的四個線程池返回的就是線程池解決方法中的兩個線程池。


2.Future接口:

Future接口是用來獲取異步計算結果的,說白了就是對具體的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;  
    } 

方法解析:
V get()獲取異步執行的結果,如果沒有結果可用,此方法會阻塞直到異步計算完成。一般我們就是調用get方法返回線程池結果。
V get(Long timeout , TimeUnit unit)獲取異步執行結果,帶時延的結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,如果阻塞時間超過設定的timeout時間,該方法將拋出異常。
boolean isDone():如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。
boolean isCanceller():如果任務完成前被取消,則返回true。
boolean cancel(boolean mayInterruptRunning) :如果任務還沒開始,執行cancel(…)方法將返回false;如果任務已經啓動,執行cancel(true)方法將以中斷執行此任務線程的方式來試圖停止任務,如果停止成功,返回true;當任務已經啓動,執行cancel(false)方法將不會對正在執行的任務線程產生影響(讓線程正常執行到完成),此時返回false;當任務已經完成,執行cancel(…)方法將返回false。mayInterruptRunning參數表示是否中斷執行中的線程。
通過方法分析我們也知道實際上Future提供了3種功能

(1)能夠中斷執行中的任務

(2)判斷任務是否執行完成

(3)獲取任務執行完成後額結果。
但是我們必須明白Future只是一個接口,我們無法直接創建對象,因此就需要其實現類FutureTask登場啦。


3.FutureTask類:

我們先來看看FutureTask的實現

public class FutureTask<V> implements RunnableFuture<V> {  

FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口的實現:

public interface RunnableFuture<V> extends Runnable, Future<V> {  
        void run();  
    }

分析:FutureTask除了實現了Future接口外還實現了Runnable接口(即可以通過Runnable接口實現線程,也可以通過Future取得線程執行完後的結果),因此FutureTask也可以直接提交給Executor執行。
最後我們給出FutureTask的兩種構造函數:

public FutureTask(Callable<V> callable) {  
    }  
    public FutureTask(Runnable runnable, V result) {  
    } 

4.Callable/Future/FutureTask的使用(封裝了異步獲取結果的Future!!!)

通過上面的介紹,我們對Callable,Future,FutureTask都有了比較清晰的瞭解了,那麼它們到底有什麼用呢?我們前面說過通過這樣的方式去創建線程的話,最大的好處就是能夠返回結果,加入有這樣的場景,我們現在需要計算一個數據,而這個數據的計算比較耗時,而我們後面的程序也要用到這個數據結果,那麼這個時Callable豈不是最好的選擇?我們可以開設一個線程去執行計算,而主線程繼續做其他事,而後面需要使用到這個數據時,我們再使用Future獲取不就可以了嗎?下面我們就來編寫一個這樣的實例

4.1 使用Callable+Future獲取執行結果

Callable實現類如下:

package com.zejian.Executor;  
import java.util.concurrent.Callable;  
/** 
 * @author zejian 
 * @time 2016年3月15日 下午2:02:42 
 * @decrition Callable接口實例 
 */  
public class CallableDemo implements Callable<Integer> {  
      
    private int sum;  
    @Override  
    public Integer call() throws Exception {  
        System.out.println("Callable子線程開始計算啦!");  
        Thread.sleep(2000);  
          
        for(int i=0 ;i<5000;i++){  
            sum=sum+i;  
        }  
        System.out.println("Callable子線程計算結束!");  
        return sum;  
    }  

}

Callable執行測試類如下:

  public class CallableTest {  
          
        public static void main(String[] args) {  
            //創建線程池  
            ExecutorService es = Executors.newSingleThreadExecutor();  
            //創建Callable對象任務  
            CallableDemo calTask=new CallableDemo();  
            //提交任務並獲取執行結果  
            Future<Integer> future =es.submit(calTask);  
            //關閉線程池  
            es.shutdown();  
            try {  
                Thread.sleep(2000);  
            System.out.println("主線程在執行其他任務");  
              
            if(future.get()!=null){  
                //輸出獲取到的結果  
                System.out.println("future.get()-->"+future.get());  
            }else{  
                //輸出獲取到的結果  
                System.out.println("future.get()未獲取到結果");  
            }  
              
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            System.out.println("主線程在執行完成");  
        }  
    }  

4.2 使用Callable+FutureTask獲取執行結果

public class CallableTest {  
          
        public static void main(String[] args) {  
    //      //創建線程池  
    //      ExecutorService es = Executors.newSingleThreadExecutor();  
    //      //創建Callable對象任務  
    //      CallableDemo calTask=new CallableDemo();  
    //      //提交任務並獲取執行結果  
    //      Future<Integer> future =es.submit(calTask);  
    //      //關閉線程池  
    //      es.shutdown();  
              
            //創建線程池  
            ExecutorService es = Executors.newSingleThreadExecutor();  
            //創建Callable對象任務  
            CallableDemo calTask=new CallableDemo();  
            //創建FutureTask  
            FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
            //執行任務  
            es.submit(futureTask);  
            //關閉線程池  
            es.shutdown();  
            try {  
                Thread.sleep(2000);  
            System.out.println("主線程在執行其他任務");  
              
            if(futureTask.get()!=null){  
                //輸出獲取到的結果  
                System.out.println("futureTask.get()-->"+futureTask.get());  
            }else{  
                //輸出獲取到的結果  
                System.out.println("futureTask.get()未獲取到結果");  
            }  
              
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            System.out.println("主線程在執行完成");  
        }  
    } 

總結一下兩者的區別:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章