1. 程式人生 > >【小家Java】Future與FutureTask的區別與聯絡

【小家Java】Future與FutureTask的區別與聯絡

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本


Future模式簡述

  • 傳統單執行緒環境下,呼叫函式是同步的,必須等待程式返回結果後,才可進行其他處理。 Futrue模式下,呼叫方式改為非同步。
  • Futrue模式的核心在於:充分利用主函式中的等待時間,利用等待時間處理其他任務,充分利用計算機資源。

所謂非同步呼叫其實就是實現一個可無需等待被呼叫函式的返回值而讓操作繼續執行的方法。在 Java 語言中,簡單的講就是另啟一個執行緒來完成呼叫中的部分計算,使呼叫繼續執行或返回,而不需要等待計算結果。但呼叫者仍需要取執行緒的計算結果。

JDK5新增了Future介面,用於描述一個非同步計算的結果。雖然 Future 以及相關使用方法提供了非同步執行任務的能力,但是對於結果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。阻塞的方式顯然和我們的非同步程式設計的初衷相違背,輪詢的方式又會耗費無謂的 CPU 資源,而且也不能及時地得到計算結果。

Future

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果等操作。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。
Future類位於java.util.concurrent包下,它是一個介面:

public interface Future<V> {
    /**
     * 方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。
     *
     * @param mayInterruptIfRunning 表示是否允許取消正在執行卻沒有執行完畢的任務,如果設定true,則表示可以取消正在執行過程中的任務。
     * @return 如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;
     * 如果任務正在執行,若mayInterruptIfRunning設定為true,則返回true,若mayInterruptIfRunning設定為false,則返回false;
     * 如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
     */
boolean cancel(boolean mayInterruptIfRunning); /** * 方法表示任務是否被取消成功 * @return 如果在任務正常完成前被取消成功,則返回 true */ boolean isCancelled(); /** * 方法表示任務是否已經完成 * @return 若任務完成,則返回true */ boolean isDone(); /** * 方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回 * @return 任務執行的結果值 * @throws InterruptedException * @throws ExecutionException */ V get() throws InterruptedException, ExecutionException; /** * 用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null(並不是丟擲異常,需要注意)。 * @param timeout 超時時間 * @param unit 超時單位 * @return * @throws InterruptedException * @throws ExecutionException * @throws TimeoutException */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }

從上面方法的註釋可以看出,Futrue提供了三種功能:
1)判斷任務是否完成;
2)能夠中斷任務;
3)能夠獲取任務執行結果。(最為常用的)

因為Future只是一個介面,所以是無法直接用來建立物件使用的,因此就有了下面的FutureTask(JDK8以前唯一實現類)。

FutureTask

public class FutureTask<V> implements RunnableFuture<V>
	public interface RunnableFuture<V> extends Runnable, Future<V>

由此看出FutureTask它既可以作為Runnable被執行緒執行,又可以作為Future得到Callable的返回值。
FutureTask提供了2個構造器:

	// 包裝callable
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

	//把Runable轉換成callable來處理(結果盡然讓傳進來,所以這個方法沒啥用)
	public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
  • FutureTask實現了Runnable,因此它既可以通過Thread包裝來直接執行,也可以提交給ExecuteService來執行。
  • FutureTask實現了Futrue可以直接通過get()函式獲取執行結果,該函式會阻塞,直到結果返回。

舉個栗子

直接使用Futrue來接收返回值:(結合callable)
    public static void main(String args[]) {
        Instant start = Instant.now();

        ExecutorService executor = Executors.newCachedThreadPool();
        //執行callable任務  拿到但繪製result
        Future<Integer> result = executor.submit(() -> {
            System.out.println("子執行緒在進行計算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        });
        Instant mid = Instant.now();
        System.out.println("Mid拿到Futrue結果物件result:" + Duration.between(start, mid).toMillis());

        try {
            System.out.println("task執行結果計算的總和為:" + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();

        Instant end = Instant.now();
        System.out.println("End拿到結果值:" + Duration.between(start, end).toMillis());
    }
輸出:
子執行緒在進行計算
Mid拿到Futrue結果物件result:98
task執行結果計算的總和為:4950
End拿到結果值:3099

我們很顯然可以發現,mid的時間很短,主執行緒馬上向下執行了,但是end的時間就比較長了,因此get()拿結果屬於阻塞方法。

FutureTask獲取結果:(結合Callbale使用)
    public static void main(String args[]) {
        Instant start = Instant.now();

        ExecutorService executor = Executors.newCachedThreadPool();
        //使用FutureTask包裝callbale任務,再交給執行緒池執行
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            System.out.println("子執行緒在進行計算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        });
        //執行緒池執行任務 這個返回值不需要了,直接就在futureTask物件裡面了
        executor.submit(futureTask);

        Instant mid = Instant.now();
        System.out.println("Mid拿到futureTask結果物件result:" + Duration.between(start, mid).toMillis());

        try {
            System.out.println("task執行結果計算的總和為:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();

        Instant end = Instant.now();
        System.out.println("End拿到結果值:" + Duration.between(start, end).toMillis());
    }
輸出:
子執行緒在進行計算
Mid拿到futureTask結果物件result:86
task執行結果計算的總和為:4950
End拿到結果值:3088

第二種方式(只是進行了簡單包裝而已):

//第二種方式,注意這種方式和第一種方式效果是類似的,只不過一個使用的是ExecutorService,一個使用的是Thread
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            System.out.println("子執行緒在進行計算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        });
        Thread thread = new Thread(futureTask);
        thread.start();

如果為了可取消性而使用 Future 但又不提供可用的結果,則可以宣告 Future<?> 形式型別、並返回 null 作為底層任務的結果。

Callable和Futrue的區別:Callable用於產生結果,Future用於獲取結果

總結

Futrue的使用和FutrueTask的使用,沒有本質的區別。所以…