1.FutrueTask概念
FutureTask一個可取消的異步計算,FutureTask 實現了Future的基本方法,提空 start cancel 操作,可以查詢計算是否已經完成,並且可以獲取計算的結果。結果只可以在計算完成之后獲取,get方法會阻塞當計算沒有完成的時候,一旦計算已經完成,那么計算就不能再次啟動或是取消。
一個FutureTask 可以用來包裝一個 Callable 或是一個runnable對象。因為FurtureTask實現了Runnable方法,所以一個 FutureTask可以提交(submit)給一個Excutor執行(excution).
2.FutureTask使用場景
FutureTask可用於異步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之后可以在外部通過FutureTask的get方法異步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。另外,FutureTask還可以確保即使調用了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。
2.1 FutureTask執行多任務計算場景
利用FutureTask和ExecutorService,可以用多線程的方式提交計算任務,主線程繼續執行其他任務,當主線程需要子線程的計算結果時,在異步獲取子線程的執行結果。

1 /** 2 * Created by zhumiao on 2018/8/17. 3 */ 4 public class FutureTest1 { 5 public static void main(String[] args) { 6 Task task = new Task();// 新建異步任務 7 FutureTask<Integer> future = new FutureTask<Integer>(task) { 8 // 異步任務執行完成,回調 9 @Override 10 protected void done() { 11 try { 12 System.out.println("future.done():" + get()); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } catch (ExecutionException e) { 16 e.printStackTrace(); 17 } 18 } 19 }; 20 // 創建線程池(使用了預定義的配置) 21 ExecutorService executor = Executors.newCachedThreadPool(); 22 executor.execute(future); 23 24 try { 25 Thread.sleep(1000); 26 } catch (InterruptedException e1) { 27 e1.printStackTrace(); 28 } 29 // 可以取消異步任務 30 // future.cancel(true); 31 try { 32 // 阻塞,等待異步任務執行完畢-獲取異步任務的返回值 33 System.out.println("future.get():" + future.get()); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } catch (ExecutionException e) { 37 e.printStackTrace(); 38 } 39 } 40 // 異步任務 41 static class Task implements Callable<Integer> { 42 // 返回異步任務的執行結果 43 @Override 44 public Integer call() throws Exception { 45 int i = 0; 46 for (; i < 10; i++) { 47 try { 48 System.out.println(Thread.currentThread().getName() + "_" 49 + i); 50 Thread.sleep(500); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 return i; 56 } 57 } 58 }
2.2 FutureTask在高並發下確保任務只執行一次
在很多高並發的環境下,往往我們只需要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的連接池,當key存在時,即直接返回key對應的對象;當key不存在時,則創建連接。對於這樣的應用場景,通常采用的方法為使用一個Map對象來存儲key和連接池對應的對應關系,典型的代碼如下面所示:

1 private Map<String, Connection> connectionPool = new HashMap<String, Connection>(); 2 private ReentrantLock lock = new ReentrantLock(); 3 4 public Connection getConnection(String key){ 5 try{ 6 lock.lock(); 7 if(connectionPool.containsKey(key)){ 8 return connectionPool.get(key); 9 } 10 else{ 11 //創建 Connection 12 Connection conn = createConnection(); 13 connectionPool.put(key, conn); 14 return conn; 15 } 16 } 17 finally{ 18 lock.unlock(); 19 } 20 } 21 22 //創建Connection(根據業務需求,自定義Connection) 23 private Connection createConnection(){ 24 return null; 25 }
在上面的例子中,我們通過加鎖確保高並發環境下的線程安全,也確保了connection只創建一次,然而確犧牲了性能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,性能大大提高,但是在高並發的情況下有可能出現Connection被創建多次的現象。這時最需要解決的問題就是當key不存在時,創建Connection的動作能放在connectionPool之后執行,這正是FutureTask發揮作用的時機,基於ConcurrentHashMap和FutureTask的改造代碼如下:

1 private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>(); 2 3 public Connection getConnection(String key) throws Exception{ 4 FutureTask<Connection>connectionTask=connectionPool.get(key); 5 if(connectionTask!=null){ 6 return connectionTask.get(); 7 } 8 else{ 9 Callable<Connection> callable = new Callable<Connection>(){ 10 @Override 11 public Connection call() throws Exception { 12 // TODO Auto-generated method stub 13 return createConnection(); 14 } 15 }; 16 FutureTask<Connection>newTask = new FutureTask<Connection>(callable); 17 connectionTask = connectionPool.putIfAbsent(key, newTask); 18 if(connectionTask==null){ 19 connectionTask = newTask; 20 connectionTask.run(); 21 } 22 return connectionTask.get(); 23 } 24 } 25 26 //創建Connection(根據業務需求,自定義Connection) 27 private Connection createConnection(){ 28 return null; 29 }
經過這樣的改造,可以避免由於並發帶來的多次創建連接及鎖的出現。