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。