Future

January 13, 2022

你可能有過這類對話的經驗:「老闆,我要一份蚵仔煎,待會來拿!」這也描述了 Future 的概念。

未來的結果

Future 就是讓你在未來取得結果。可以將想執行的工作交給 Future,Future 會使用另一執行緒來進行工作,你就可以先忙別的事去,過些時候,再從 Future 取得結果。

如果未來某個時間點,結果尚未準備好,就想取得呢?這就要看需求了,看是要等待,或者是指定等待時間,或者是拋出例外,也可以讓 Future 物件提供查詢方法,讓客戶端查詢結果已備妥後再拿取。

來看看 Future 的基本實作方式:

class Future<T> implements Runnable {
    private Callable<T> callable;
    private T r;
    
    Future(Callable<T> callable) {
        this.callable = callable;
    }
    
    boolean isDone() {
        return r != null;
    }
    
    synchronized T get() throws InterruptedException {
        while(r == null) {
            wait();
        }
        return r;
    }
    
    public void run() {
        try {
            synchronized(this) {
                r = callable.call();
                notify();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static <T> Future<T> submit(Callable<T> callable) {
        var future = new Future<>(callable);
        new Thread(future).start();
        return future;
    }
}

這邊的 get 實作,是有結果才會傳回,沒有結果時進入等待,為了隱藏執行緒的建立細節,簡單地在 Future 上設計了 staticsubmit 方法,其中直接 new 建立 Thread,實際上,也可以透過執行緒池之類的方式來取得執行緒。

就以上這個 Future 實作而言,可以如下使用:

static long fibonacci(long n) {
    if(n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

public static void main(String[] args) throws InterruptedException {
    var future = Future.submit(() -> fibonacci(30));
    out.println("老闆,我要第 30 個費式數,待會來拿...");
    while(!future.isDone()) {
        out.println("忙別的事去...");
    }
    out.printf("第 30 個費式數:%d%n", future.get());   
}

Java 的 Future

Java 的並行 API 中,規範了 java.util.concurrent.Future 等介面,例如 java.util.concurrent.FutureTaskFuture 的實作類別,建構時可傳入 Callable 實作物件指定的執行的內容。

var future = new FutureTask<>(() -> fibonacci(30));
new Thread(future).start();
out.println("老闆,我要第 30 個費式數,待會來拿...");
while(!future.isDone()) {
    out.println("忙別的事去...");
}
out.printf("第 30 個費式數:%d%n", future.get());       

若不想自行建立、管理 Thread,可以透過 ExecutorService

var service = Executors.newCachedThreadPool();
var future = service.submit(() -> fibonacci(30));
out.println("老闆,我要第 30 個費式數,待會來拿...");
while(!future.isDone()) {
    out.println("忙別的事去...");
}
out.printf("第 30 個費式數:%d%n", future.get());  

Executors.newCachedThreadPool 會建立 ExecutorService 的實例,具有執行緒池的功能,ExecutorService 定義了 submit 方法傳回 Future 實例。