FutureTask的用法及兩種經常使用的使用場景

FutureTask可用於異步獲取執行結果或取消執行任務的場景。經過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,以後能夠在外部經過FutureTask的get方法異步獲取執行結果,所以,FutureTask很是適合用於耗時的計算,主線程能夠在完成本身的任務後,再去獲取結果。另外,FutureTask還能夠確保即便調用了屢次run方法,它都只會執行一次Runnable或者Callable任務,或者經過cancel取消FutureTask的執行等。java

1.執行多任務計算

FutureTask執行多任務計算的使用場景安全

利用FutureTask和ExecutorService,能夠用多線程的方式提交計算任務,主線程繼續執行其餘任務,當主線程須要子線程的計算結果時,在異步獲取子線程的執行結果。多線程

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<FutureTask<Integer>>();
        // 建立線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            // 傳入Callable對象建立FutureTask對象
            FutureTask<Integer> ft = new FutureTask<Integer>(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) {
            result = iniResult;
            this.taskName = taskName;
            System.out.println("生成子線程計算任務: " + taskName);
        }

        public String getTaskName() {
            return this.taskName;
        }

        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub

            for (int i = 0; i < 100; i++) {
                result = +i;
            }
            // 休眠5秒鐘,觀察主線程行爲,預期的結果是主線程會繼續執行,到要取得FutureTask的結果是等待直至完成。
            Thread.sleep(5000);
            System.out.println("子線程計算任務: " + taskName + " 執行完成!");
            return result;
        }
    }
}
生成子線程計算任務: 0
生成子線程計算任務: 1
生成子線程計算任務: 2
生成子線程計算任務: 3
生成子線程計算任務: 4
生成子線程計算任務: 5
生成子線程計算任務: 6
生成子線程計算任務: 7
生成子線程計算任務: 8
生成子線程計算任務: 9
全部計算任務提交完畢, 主線程接着幹其餘事情!
子線程計算任務: 0 執行完成!
子線程計算任務: 2 執行完成!
子線程計算任務: 3 執行完成!
子線程計算任務: 4 執行完成!
子線程計算任務: 1 執行完成!
子線程計算任務: 8 執行完成!
子線程計算任務: 7 執行完成!
子線程計算任務: 6 執行完成!
子線程計算任務: 9 執行完成!
子線程計算任務: 5 執行完成!
多任務計算後的總結果是:990

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的改造代碼以下:ide

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;
    }

通過這樣的改造,能夠避免因爲併發帶來的屢次建立鏈接及鎖的出現。高併發

Contact

  • 做者:鵬磊
  • 出處:http://www.ymq.io
  • Email:admin@souyunku.com
  • 版權歸做者全部,轉載請註明出處
  • Wechat:關注公衆號,搜雲庫,專一於開發技術的研究與知識分享

關注公衆號-搜雲庫

相關文章
相關標籤/搜索