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

參考博客: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)若是已經存在,那麼不會覆蓋已有的值,直接返回已經存在的值。

相關文章
相關標籤/搜索