Java的Future介面

jlttt發表於2022-02-28

Java的Future介面

Java 中的 Future 介面和其實現類 FutureTask,代表了非同步計算的結果。

1. Future介面簡介

Future 是非同步計算結果的容器介面,它提供了下面這些功能:

  • 在等待非同步計算完成時檢查計算結果是否完成
  • 在非同步計算完成後獲取計算結果
  • 在非同步計算完成前取消

Future 可以用於耗時的非同步計算任務。例如我們把 Runnable 介面或 Callable 介面的實現類提交到執行緒池時,執行緒池會返回一個 FutureTask 物件。

<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable<T> task, T result)

下文會再解釋 FutureTask,這是 Future 介面的一個實現類。

Future 介面提供了下面這些方法

Modifier and Type Method Description
boolean cancel(boolean mayInterruptIfRunning) 嘗試取消執行此任務。
V get() 等待計算完成,然後檢索其結果。
V get(long timeout, TimeUnit unit) 如果需要等待最多在給定的時間計算完成,然後檢索其結果(如果可用)。
boolean isCancelled() 如果此任務在正常完成之前被取消,則返回 true
boolean isDone() 返回 true如果任務已完成。

2. FutureTask的使用

可以將 FutureTask 交給 Executor 執行,也可以通過ExecutorService.submit(...)方法返回一個 FutureTask,然後執行 get 方法或 cancel 方法。

也可以單獨使用 FutureTask,比如下面的程式碼就實現了一種需求:一個執行緒必須等待另一個執行緒把某個任務執行完後它才能繼續執行。假設有多個執行緒執行若干個任務,每個任務最多隻能同時被執行一次,多個執行緒試圖執行同一個任務時,只允許一個執行緒執行任務,其他執行緒等待這個任務執行完後才能繼續執行。

public class ConcurrentTask {

    private final ConcurrentMap<Object, Future<String>> taskCache = new ConcurrentHashMap<Object, Future<String>>();

    private String executionTask(final String taskName) throws ExecutionException, InterruptedException {
        while (true) {
            Future<String> future = taskCache.get(taskName); //1.1,2.1
            if (future == null) {
                // 建立 Task
                Callable<String> task = new Callable<String>() {
                    public String call() throws InterruptedException {
                        //......
                        return taskName;
                    }
                };
                //1.2 建立 FutureTask
                FutureTask<String> futureTask = new FutureTask<>(task);
                future = taskCache.putIfAbsent(taskName, futureTask); //1.3
                // 如果是第一次放入,則嘗試執行
                if (future == null) {
                    future = futureTask;
                    futureTask.run(); //1.4執行任務
                }
            }

            try {
                return future.get(); //1.5,2.2執行緒在此等待任務執行完成
            } catch (CancellationException e) {
                taskCache.remove(taskName, future);
            }
        }
    }
}

相信不難理解,下面是執行的示意圖。

image-20220227133720768

3. FutureTask的實現

FutureTask 的實現基於佇列同步器 QAS。

基於複合優先於繼承的原則,FutureTask 宣告瞭一個內部私有的,繼承於 AQS 的子類 Sync,這對 FutureTask 所有公有方法的呼叫都會委託給這個內部子類。

image-20220227134256038

FutureTask 的get方法會呼叫AQS.acquireSharedInterruptibly(int arg)方法,執行過程如下:

  1. 呼叫AQS.acquireSharedInterruptibly(int arg)方法,首先回撥子類 Sync 中的方法tryAcqurieShared判斷acquire操作是否可以成功。acquire操作成功的條件為:state 為執行完成狀態 RAN 或已取消狀態 CANCELLED 且 runner 不為 null。
  2. 如果成功則get方法立即返回,失敗則到執行緒等待佇列中去等待其他執行緒執行release
  3. 當其他執行緒執行release,如FutureTask.run()FutureTask.cancel(),喚醒當前執行緒後。當前執行緒再次執行tryAcquireShared將返回值 1,當前執行緒離開等待佇列並喚醒後續執行緒。
  4. 最後返回結果或丟擲異常。

FutureTask 的 run 方法執行過程如下:

  1. 執行建構函式中指定的任務。
  2. 原子方式更新同步狀態,呼叫AQS.compareAndSetState
  3. 如果上面的原子操作成功,設定代表計算結果的變數 result 的值為Callable.call()的返回指,然後呼叫AQS.releaseShared(int arg)
  4. AQS.releaseShared(int arg)首先回撥 Sync 中的tryReleaseShared(arg)來執行release。這個方法喚醒等待佇列中第一個執行緒。
  5. 呼叫FutureTask.done()

當呼叫FutureTask.get()方法時,如果 FutureTask 不是處於執行完成狀態 RAN 或已取消狀態 CANCELLED。當前執行執行緒將到 AQS 的執行緒等待佇列中等待。

image-20220227142111557

相關文章