CompletionService和CompletableFuture

大軍發表於2022-02-09

我們在jdk1.8之前,都是用FutureTask的get方法來獲取非同步執行的結果。

在演示之前,先貼一下共用的程式碼。

ConcurrentSupport:

public class ConcurrentSupport {
    public static String processOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getNow() + "#one";
    }

    public static String processTwo() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getNow() + "#two";
    }

    public static String processThree() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getNow() + "#three";
    }

    public static String getNow() {
        return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
    }
}

TaskA:

class TaskA implements Callable {
    @Override
    public Object call() throws Exception {
        return ConcurrentSupport.processOne();
    }
}

TaskB:

class TaskB implements Callable {
    @Override
    public Object call() throws Exception {
        return ConcurrentSupport.processTwo();
    }
}

TaskC:

class TaskC implements Callable {
    @Override
    public Object call() throws Exception {
        return ConcurrentSupport.processThree();
    }
}

普通情況

比如有3個任務,分別耗時1s、2s、3s,在同步執行的時候,整個耗時就是6s。

public class NormalDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        System.out.println(ConcurrentSupport.processOne());
        System.out.println(ConcurrentSupport.processTwo());
        System.out.println(ConcurrentSupport.processThree());
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
    }
}

FutureTask

用FutureTask的情況,就是取最長時間的那個,所以最終時間是3s。

public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 建立執行緒池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        long start = System.currentTimeMillis();
        // 建立FutureTask
        FutureTask taskA = new FutureTask(new TaskA());
        FutureTask taskB = new FutureTask(new TaskB());
        FutureTask taskC = new FutureTask(new TaskC());
        executor.submit(taskA);
        executor.submit(taskB);
        executor.submit(taskC);
        System.out.println(taskC.get());
        System.out.println(taskA.get());
        System.out.println(taskB.get());
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
    }
}

CompletionService

但是FutureTask也有一個小瑕疵,比如上面的TaskC執行的時間最長,直接把TaskA和TaskB的列印任務給阻塞了,列印的結果是three、one、two。

有木有辦法是哪個任務先執行成功,就先列印(或者對這個結果其他處理)這個結果呢?

CompletionService做的就是這個事情。從結果可以看出列印one、two、three。

public class CompletionServiceDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 建立執行緒池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        long start = System.currentTimeMillis();
        // 建立CompletionService
        CompletionService<String> cs = new ExecutorCompletionService<>(executor);
        // 用於儲存Future物件
        List<Future<String>> futures = new ArrayList<>(3);
        // 提交FutureTask
        futures.add(cs.submit(new TaskC()));
        futures.add(cs.submit(new TaskA()));
        futures.add(cs.submit(new TaskB()));
        for (int i = 0; i < 3; i++) {
            String result = cs.take().get();
            System.out.println(result);
        }
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
    }
}

CompletableFuture

如果有一個任務是這樣的,A1執行完執行A2,B1執行完執行B2,A2和B2執行完,再執行C。

JDK1.8提供了CompletableFuture這個優雅的解決方案。

比如下面的例子,就是f1和f2執行完後,才執行f3。

CompletableFuture的方法中,runAsync是沒有返回值的,supplyAsync是有返回值的。

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
                    String one = ConcurrentSupport.processOne();
                    System.out.println(one);
                }
        );
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
                    String two = ConcurrentSupport.processTwo();
                    System.out.println(two);
                    return two;
                }
        );
        CompletableFuture<String> f3 =
                f1.thenCombine(f2, (__, tf) -> {
                            System.out.println("f3#" + tf);
                            return "f3";
                        }
                );
        f3.join();
    }
}

相關文章