JDK 實現
public class FutureTest { public static void main(String[] args) throws Exception { ExecutorService es = Executors.newFixedThreadPool(10); Future<Integer> f = es.submit(() ->{ Thread.sleep(5000); // 結果 return 100; }); Integer result = f.get(); System.out.println(result); // 也可以輪詢等結束 // while (f.isDone()) { // System.out.println(result); // } } }
雖然這些方法提供了異步執行任務的能力,但是對於結果的獲取卻還是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。
阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的CPU資源,而且也不能及時的得到計算結果。
Java的一些框架,
Netty,自己擴展了Java的 Future 接口,提供了 addListener 等多個擴展方法。
Google的guava也提供了通用的擴展Future:ListenableFuture 、 SettableFuture 以及輔助類 Futures 等,方便異步編程。
Java 在JDK1.8 這個版本中增加了一個能力更強的Future類:CompletableFuture 。它提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的複雜性,提供了函數式編程的能力,可以通過回調的方式處理計算結果。下面來看看這幾種方式。
Netty-Future
引入Maven依賴:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.50.Final</version> </dependency>
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.7</version> </dependency>
package com.vipsoft; import cn.hutool.core.date.DateUtil; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; public class FutureTest { public static void main(String[] args) throws InterruptedException { EventExecutorGroup group = new DefaultEventExecutorGroup(4); System.out.println("開始:" + DateUtil.now()); Future<Integer> f = group.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("開始耗時計算:" + DateUtil.now()); Thread.sleep(5000); System.out.println("結束耗時計算:" + DateUtil.now()); int a = 0; int b = 1; int c = b / a; return 100; } }); //通過監聽,待線程結束後,自動觸發,避免了主線程 的阻塞和等待 f.addListener(new FutureListener<Object>() { @Override public void operationComplete(Future<Object> objectFuture) throws Exception { System.out.println("計算結果:" + objectFuture.get()); } }); System.out.println("結束:" + DateUtil.now()); // 不讓守護線程退出 new CountDownLatch(1).await(); } }
在Listener添加成功之後,會立即檢查狀態,如果任務已經完成立刻進行回調,通過監聽,待線程結束後,自動觸發,避免了主線程 的阻塞和等待
Guava-Future
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.7</version> </dependency>
package com.vipsoft; import cn.hutool.core.date.DateUtil; import com.google.common.util.concurrent.*; import javax.annotation.Nullable; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FutureTest { public static void main(String[] args) throws InterruptedException { System.out.println("開始:" + DateUtil.now()); ExecutorService executorService = Executors.newFixedThreadPool(10); ListeningExecutorService service = MoreExecutors.listeningDecorator(executorService); ListenableFuture<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("開始耗時計算:" + DateUtil.now()); Thread.sleep(5000); System.out.println("結束耗時計算:" + DateUtil.now()); return 100; } }); //增加回調函數,一般用於不在乎執行結果的地方 future.addListener(new Runnable() { @Override public void run() { System.out.println("調用成功--不關心結果"); } }, executorService); //通過addCallback 獲得結果 Futures.addCallback(future, new FutureCallback<Integer>() { @Override public void onSuccess(@Nullable Integer result) { System.out.println("成功,計算結果:" + result); } @Override public void onFailure(Throwable t) { System.out.println("失敗"); } }, executorService); System.out.println("結束:" + DateUtil.now()); new CountDownLatch(1).await(); } }
CompletableFuture
package com.vipsoft; import cn.hutool.core.date.DateUtil; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; public class FutureTest { public static void main(String[] args) throws InterruptedException { System.out.println("開始:" + DateUtil.now()); CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println("開始耗時計算:" + DateUtil.now()); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("結束耗時計算:" + DateUtil.now()); return 100; }); //使用 thenCompose 或者 thenComposeAsync 等方法可以實現回調的回調,且寫出來的方法易於維護。 completableFuture = completableFuture.thenCompose(i -> { return CompletableFuture.supplyAsync(() -> { System.out.println("在回調的回調中執行耗時操作..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return i + 200; }); }); completableFuture.whenComplete((result, e) -> { System.out.println("回調結果:" + result); }); System.out.println("結束:" + DateUtil.now()); new CountDownLatch(1).await(); } }
JDK1.8 已經提供了一種更爲高級的回調方式:CompletableFuture,不需要引入任何第三方的依賴,爲Future模式增加回調功能就不需要阻塞等待結果的返回並且不需要消耗無謂的CPU資源去輪詢處理狀態,JDK8之前使用Netty或者Guava提供的工具類,JDK8之後則可以使用自帶的 CompletableFuture 類。Future 有兩種模式:將來式和回調式。而回調式會出現回調地獄的問題,由此衍生出了 Promise 模式來解決這個問題。這纔是 Future 模式和 Promise 模式的相關性。