1. 程式人生 > >FutureTask的用法及兩種常用的使用場景 + FutureTask的方法執行示意圖

FutureTask的用法及兩種常用的使用場景 + FutureTask的方法執行示意圖

 

from:  https://blog.csdn.net/linchunquan/article/details/22382487

 

FutureTask可用於非同步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接呼叫其run方法或者放入執行緒池執行,之後可以在外部通過FutureTask的get方法非同步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主執行緒可以在完成自己的任務後,再去獲取結果。另外,FutureTask還可以確保即使呼叫了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。

1. FutureTask執行多工計算的使用場景

利用FutureTask和ExecutorService,可以用多執行緒的方式提交計算任務,主執行緒繼續執行其他任務,當主執行緒需要子執行緒的計算結果時,在非同步獲取子執行緒的執行結果。
 

2. FutureTask在高併發環境下確保任務只執行一次

在很多高併發的環境下,往往我們只需要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的連線池,當key存在時,即直接返回key對應的物件;當key不存在時,則建立連線。對於這樣的應用場景,通常採用的方法為使用一個Map物件來儲存key和連線池對應的對應關係,典型的程式碼如下面所示:

    private Map<String, Connection> connectionPool = new HashMap<String, Connection>();
    private ReentrantLock lock = new ReentrantLock();
    
    public Connection getConnection(String key){
        try{
            lock.lock();
            if(connectionPool.containsKey(key)){
                
return connectionPool.get(key); } else{ //建立 Connection Connection conn = createConnection(); connectionPool.put(key, conn); return conn; } } finally{ lock.unlock(); } } //建立Connection private Connection createConnection(){ return null; }

在上面的例子中,我們通過加鎖確保高併發環境下的執行緒安全,也確保了connection只建立一次,然而確犧牲了效能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,效能大大提高,但是在高併發的情況下有可能出現Connection被建立多次的現象。這時最需要解決的問題就是當key不存在時,建立Connection的動作能放在connectionPool之後執行,這正是FutureTask發揮作用的時機,基於ConcurrentHashMap和FutureTask的改造程式碼如下:

    private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>();
    
    public Connection getConnection(String key) throws Exception{
        FutureTask<Connection>connectionTask=connectionPool.get(key);
        if(connectionTask!=null){
            return connectionTask.get();
        }
        else{
            Callable<Connection> callable = new Callable<Connection>(){
                @Override
                public Connection call() throws Exception {
                    // TODO Auto-generated method stub
                    return createConnection();
                }
            };
            FutureTask<Connection>newTask = new FutureTask<Connection>(callable);
            connectionTask = connectionPool.putIfAbsent(key, newTask);
            if(connectionTask==null){
                connectionTask = newTask;
                connectionTask.run();
            }
            return connectionTask.get();
        }
    }
    
    //建立Connection
    private Connection createConnection(){
        return null;
    }

經過這樣的改造,可以避免由於併發帶來的多次建立連線及鎖的出現。

 

FutureTask的方法執行示意圖: 

from: https://blog.csdn.net/javazejian/article/details/50896505 

 

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

<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個方法提交給執行緒池去執行即可。

我們根據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。