ListenalbeFuture的使用總結

LJWLgl發表於2019-04-15

為了提高任務處理速度,我們經常會將一些可並行處理的步驟用非同步的方式去處理,如果想要獲取非同步計算的結果,在Java 8之前更多的用的是Future + Callable的方式來實現,下面是使用Future和Callable的一個demo,其中的是executor.submit()方法實際返回的就是FutureTask的例項,另外Future的get方法會一直阻塞直至獲取結果。

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable(3, 10));
        // get方法會阻塞,直至獲取結果
        System.out.println(future.get());
        executor.shutdown();
    }
}

class MyCallable implements Callable<Integer> {
    private int a;
    private int b;

    public MyCallable(int a, int b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public Integer call() throws Exception {
        return a * b;
    }
}
複製程式碼

雖然Future已經相關方法提供了非同步程式設計的能力,但是獲取結果十分不方便,只能通過阻塞或者輪詢的方式獲取結果,阻塞的方式顯然與我們非同步程式設計的初衷相違背,而且輪詢的方式也很消耗的CPU資源,計算結果也不能及時拿到。面對這種情況,為什麼不採用一種類似觀察者模式的方式,當結果結算完成後實時通知到監聽任務呢?著名的guava包就提供了擴充Future,如ListenableFuture和SettableFuture,以及輔助類Futures。JDK 8中也提供類似ListenableFutureCompletableFuture介面,該介面包含很多api,後續的文章會逐一介紹。下面我們主要介紹一下Guava Future的使用。

引入Guava

最新版本的Guava可到Maven Center中查詢

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>
複製程式碼

建立ListeningExecutorService

Guava為了支援自己的Listerner模式,新建了一種ExecutorService,叫做ListeningExecutorService,我們可以使用MoreExecutor建立它

// 建立一個由invoke執行緒執行的執行緒池
 ListeningExecutorService executorService = MoreExecutors.newDirectExecutorService();
 // 裝飾自定義的執行緒池返回
 ListeningExecutorService executorService1 = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
複製程式碼

執行緒池建立完畢後,我們就可以建立ListenableFuture

ListenableFuture<?> listenableFuture = executorService.submit(new MyCallable(3, 10));
複製程式碼

新增監聽(addListener)

ListenableFuture介面擴充套件自Future介面,並新增了一個新方法 addListener,該方法是給非同步任務新增監聽

  listenableFuture.addListener(() -> {
        System.out.println("listen success");
        doSomeThing();
    }, executorService);
複製程式碼

新增回撥(Futures.addCallBack)

addListener方法不支援獲取返回值,如果需要獲取返回值,可以使用Futures.addCallBack靜態方法,該類是對JDK Future的擴充

// FutureCallback介面包含onSuccess()、onFailure()兩個方法
Futures.addCallback(listenableFuture, new FutureCallback<Object>() {
    @Override
    public void onSuccess(@Nullable Object result) {
        System.out.println("res: " + result);
    }

    @Override
    public void onFailure(Throwable t) {}
}, executorService);
複製程式碼

合併多個Future(Futures.allAsList)

如果需要同時獲取多個Future的值,可以使用Futures.allAsList,需要注意的是如果任何一個Future在執行時出現異常,都會只執行onFailure()方法,如果想獲取到正常返回的Future,可以使用Futures.successfulAsList方法,該方法會將失敗或取消的Future的結果用null來替代,不會讓程式進入onFailure()方法

ListenableFuture<Integer> future1 = executorService.submit(() -> 1 + 2);
ListenableFuture<Integer> future2 = executorService.submit(() -> Integer.parseInt("3q"));
ListenableFuture<List<Object>> futures = Futures.allAsList(future1, future2);
futures = Futures.successfulAsList(future1, future2);

Futures.addCallback(futures, new FutureCallback<List<Object>>() {
    @Override
    public void onSuccess(@Nullable List<Object> result) {
        System.out.println(result);
    }
    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
}, executorService);
複製程式碼

返回值轉換(Futures.transform)

如果需要對返回值做處理,可以使用Futures.transform方法,它是同步方法,另外還有一個非同步方法Futures.transformAsync

// 原Future
ListenableFuture<String> future3 = executorService.submit(() -> "hello, future");
// 同步轉換
ListenableFuture<Integer> future5 = Futures.transform(future3, String::length, executorService);
// 非同步轉換
ListenableFuture<Integer> future6 = Futures.transformAsync(future3, input -> Futures.immediateFuture(input.length()), executorService);
複製程式碼

immediateFuture和immediateCancelledFuture

immediateFuture該方法會立即返回一個待返回值的ListenableFutureimmediateCancelledFuture會返回一個立即取消的ListenableFuture,所以它返回的Future的isDone方法始終是false

JdkFutureAdapters

該方法可以將JDK Future轉成ListenableFuture

Future<String> stringFuture = Executors.newCachedThreadPool().submit(() -> "hello,world");
ListenableFuture<String> future7 = JdkFutureAdapters.listenInPoolThread(stringFuture);
System.out.println(future7.get());
複製程式碼

SettableFuture

SettableFuture可以認為是一種非同步轉同步工具,可以它在指定時間內獲取ListenableFuture的計算結果

SettableFuture<Integer> settableFuture = SettableFuture.create();
ListenableFuture<Integer> future11 = executorService.submit(() -> {
    int sum = 5 + 6;
    settableFuture.set(sum);
    return sum;
});
// get設定超時時間 
System.out.println(settableFuture.get(2, TimeUnit.SECONDS));
複製程式碼

原文地址:ListenalbeFuture的使用總結