J.U.C之FutureTask

本篇記錄一下FutureTask的個人理解

FutureTask可用於異步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之後可以在外部通過FutureTask的get方法異步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主線程可以在完成自己的任務後,再去獲取結果。另外,FutureTask還可以確保即使調用了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class FutureTaskTest {


    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask(new Callable() {
            public String call() throws Exception {
                System.out.println("futureTask開始做一些事情" + new SimpleDateFormat(" HH:mm:ss").format(new Date()));
                Thread.sleep(5000);
                return "做完了";
            }
        });

        new Thread(futureTask).start();
        System.out.println("main線程futureTask啓動之後做其他的一些事情" + new SimpleDateFormat(" HH:mm:ss").format(new Date()));
        Thread.sleep(2000);//main線程睡眠2秒  表示做其他事情需要的時間
        /**
         * 下面獲取futureTask的線程去做的事情的結果
         * 如果futureTask沒有執行完
         * 那麼程序會一直阻塞在這裏
         * 直到futureTask執行完成之後  纔會繼續往下執行
         */
        String res = futureTask.get();
        System.out.println("futureTask的執行結果是:" + res + "   " + new SimpleDateFormat(" HH:mm:ss").format(new Date()));
    }

}

上面這段代碼 演示了futureTask的基本使用方式 他的執行結果是:

main線程futureTask啓動之後做其他的一些事情 17:54:53
futureTask開始做一些事情 17:54:53
futureTask的執行結果是:做完了    17:54:58

從上面這個執行結果可以看出來,當程序啓動後,main線程和 futureTask各自開始執行,5秒之後輸出了futureTask的執行結果。
這可以體現出,futureTask可以拿到線程執行的結果,如果futureTask.get() 的時候 futureTask和沒有執行完成,那麼程序就會一直阻塞,知道futureTask執行完成之後纔會繼續開始往下執行。

補充一個《java併發編程實戰》第5章的例子:
這個例子使用FutureTask處理了一個緩存計算結果的功能:

Computable類:

public interface Computable<A, V> {
    V compute(A arg) throws InterruptedException;
}

ExpensiveFuntion類:

import java.math.BigInteger;

public class ExpensiveFuntion implements Computable<String, BigInteger> {

    @Override
    public BigInteger compute(String arg) throws InterruptedException {
        //做一些耗時的處理
        return new BigInteger(arg);
    }
}

Memoizer類:

import java.util.concurrent.*;

public class Memoizer<A, V> implements Computable<A, V>{

    private final ConcurrentHashMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

    private final Computable<A, V> c;

    public Memoizer(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(final A arg) throws InterruptedException {
        while (true){
            Future<V> f = cache.get(arg);                               //先看是不是已經緩存過了處理結果
            if(f == null){                                              //處理結果如果爲空
                Callable<V> eval = new Callable<V>() {
                    @Override
                    public V call() throws InterruptedException {
                        return c.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<>(eval);              //將任務放入FutureTask準備執行
                f = cache.putIfAbsent(arg, ft);                         //把任務放入ConcurrentHashMap  使用的是putIfAbsent
                if(f == null) {
                    f = ft;
                    ft.run();                                           //開始執行  這裏將會調用c.compute()
                }
            }
            try{
                return f.get();                                         //如果計算完成   那麼將返回處理結果
            } catch (CancellationException e){
                cache.remove(arg, f);                                   //捕獲CancellationException的話說明處理被取消,那麼移除無效的緩存
            } catch (ExecutionException e){
                throw launderThrowable(e.getCause());
            }
        }
    }

}

這裏只是把《java併發編程實戰》的代碼貼出來,寫了一些註釋,感興趣的可以去這本書的第5章第6小節開始看

個人淺薄理解,歡迎補充

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章