參考博客:https://blog.csdn.net/linchunquan/article/details/22382487java
FutureTask可用於異步獲取執行結果或取消執行任務的場景。經過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,以後能夠在外部經過FutureTask的get方法異步獲取執行結果,所以,FutureTask很是適合用於耗時的計算,主線程能夠在完成本身的任務後,再去獲取結果。另外,FutureTask還能夠確保即便調用了屢次run方法,它都只會執行一次Runnable或者Callable任務,或者經過cancel取消FutureTask的執行等。sql
1. FutureTask執行多任務計算的使用場景安全
利用FutureTask和ExecutorService,能夠用多線程的方式提交計算任務,主線程繼續執行其餘任務,當主線程須要子線程的計算結果時,在異步獲取子線程的執行結果。多線程
package com.cy.test.future; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class FutureTaskForMultiCompute { public static void main(String[] args) { FutureTaskForMultiCompute inst = new FutureTaskForMultiCompute(); //建立任務集合 List<FutureTask<Integer>> taskList = new ArrayList<>(); ExecutorService exec = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { // 傳入Callable對象建立FutureTask對象 FutureTask<Integer> ft = new FutureTask<>(inst.new ComputeTask(i, "" + i)); taskList.add(ft); // 提交給線程池執行任務,也能夠經過exec.invokeAll(taskList)一次性提交全部任務; exec.submit(ft); } System.out.println("全部計算任務提交完畢, 主線程接着幹其餘事情!"); // 開始統計各計算線程計算結果 Integer totalResult = 0; for (FutureTask<Integer> ft : taskList) { try { //FutureTask的get方法會自動阻塞,直到獲取計算結果爲止 totalResult = totalResult + ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } // 關閉線程池 exec.shutdown(); System.out.println("多任務計算後的總結果是:" + totalResult); } private class ComputeTask implements Callable<Integer> { private Integer result = 0; private String taskName = ""; public ComputeTask(Integer iniResult, String taskName) { this.result = iniResult; this.taskName = taskName; System.out.println("生成子線程計算任務: " + taskName); } @Override public Integer call() throws Exception { for (int i = 0; i < 100; i++) { result = +i; } // 休眠5秒鐘,觀察主線程行爲,預期的結果是主線程會繼續執行,到要取得FutureTask的結果時等待直至完成。 Thread.sleep(5000); System.out.println("子線程計算任務: " + taskName + " 執行完成!" + "result:" + result); return result; } } }
console:併發
生成子線程計算任務: 0 生成子線程計算任務: 1 生成子線程計算任務: 2 生成子線程計算任務: 3 生成子線程計算任務: 4 生成子線程計算任務: 5 生成子線程計算任務: 6 生成子線程計算任務: 7 生成子線程計算任務: 8 生成子線程計算任務: 9 全部計算任務提交完畢, 主線程接着幹其餘事情! 子線程計算任務: 0 執行完成!result:99 子線程計算任務: 3 執行完成!result:99 子線程計算任務: 4 執行完成!result:99 子線程計算任務: 2 執行完成!result:99 子線程計算任務: 1 執行完成!result:99 子線程計算任務: 9 執行完成!result:99 子線程計算任務: 8 執行完成!result:99 子線程計算任務: 7 執行完成!result:99 子線程計算任務: 6 執行完成!result:99 子線程計算任務: 5 執行完成!result:99 多任務計算後的總結果是:990
2. FutureTask在高併發環境下確保任務只執行一次異步
在不少高併發的環境下,每每咱們只須要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的鏈接池,當key存在時,即直接返回key對應的對象;當key不存在時,則建立鏈接。對於這樣的應用場景,一般採用的方法爲使用一個Map對象來存儲key和鏈接池對應的對應關係,典型的代碼以下面所示:ide
package com.cy.test.future; import java.sql.Connection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; public class ConnectionPool { 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 conn = createConnection(); connectionPool.put(key, conn); return conn; } } finally { lock.unlock(); } } private Connection createConnection() { return null; } }
在上面的例子中,咱們經過加鎖確保高併發環境下的線程安全,也確保了connection只建立一次,然而確犧牲了性能。改用ConcurrentHash的狀況下,幾乎能夠避免加鎖的操做,性能大大提升,可是在高併發的狀況下有可能出現Connection被建立屢次的現象。這時最須要解決的問題就是當key不存在時,建立Connection的動做能放在connectionPool以後執行,這正是FutureTask發揮做用的時機,基於ConcurrentHashMap和FutureTask的改造代碼以下:高併發
package com.cy.test.future; import java.sql.Connection; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.FutureTask; public class ConnectionPool2 { private ConcurrentHashMap<String, FutureTask<Connection>> connectionPool = new ConcurrentHashMap<>(); public Connection getConnection(String key) throws Exception { FutureTask<Connection> connectionTask = connectionPool.get(key); if (connectionTask != null) { return connectionTask.get(); } else { FutureTask<Connection> newTask = new FutureTask<>(new Callable<Connection>() { @Override public Connection call() throws Exception { return createConnection(); } }); connectionTask = connectionPool.putIfAbsent(key, newTask); if (connectionTask == null) { connectionTask = newTask; connectionTask.run(); } return connectionTask.get(); } } private Connection createConnection() { return null; } }
通過這樣的改造,能夠避免因爲併發帶來的屢次建立鏈接及鎖的出現。性能
ConcurrentHashMap的putIfAbsent:this
putIfAbsent方法主要是在向ConcurrentHashMap中添加鍵—值對的時候,它會先判斷該鍵值對是否已經存在。
(1)若是是新的記錄,那麼會向map中添加該鍵值對,並返回null。 (2)若是已經存在,那麼不會覆蓋已有的值,直接返回已經存在的值。