為了提高任務處理速度,我們經常會將一些可並行處理的步驟用非同步的方式去處理,如果想要獲取非同步計算的結果,在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中也提供類似ListenableFuture
的CompletableFuture
介面,該介面包含很多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
該方法會立即返回一個待返回值的ListenableFuture
,immediateCancelledFuture
會返回一個立即取消的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));
複製程式碼