异步编程中主要关心的是线程间通信问题,Java中我们常用的一般有三种方式:
以上第一种需要通过阻塞线程实现同步,资源无法充分利用,不适合频繁的异步任务处理;第二种callback的方式,当出现多个连续异步调用时难以组合在一起,会代码难以阅读以及难以维护(称为“Callback Hell”);第三种Futures避免了callback缩进的问题,能够以顺序的结构组织代码,但调用get仍然会阻塞代码,1.8引入了CompletableFuture提供了when、then等方法防止阻塞,但是使用体验仍然差强人意。
由于以上三种方式或多或少都存在不足,响应式编程作为新的异步通信方式逐渐流行起来。
响应式编程是一种关注于数据流(data streams)和变化传递(propagation of change)的异步编程方式。它可以用既有的编程语言表达静态(如数组)或动态(如事件源)的数据流。
RxJava是Java中最常用的响应式编程框架。RxJava的声明式API较好地解决了Callback带来的缩进问题;基于函数式编程思想的各种操作符可以让代码避免同步get这种阻塞式的逻辑。Java8的StreamApi也是一种响应式编程框架具备声明式的Api,但是它只能处理Cold流,不能像RxJava那样处理Hot流,也没有线程切换的能力,适用范围比较窄。
接下来我们通过实例来看一下RxJava相对于其他几种异步通信方式的优势:
先通过一个例子于Callback方式做一个对比:
在用户的UI上展示用户喜欢的top 5个商品的详细信息,如果不存在的话则调用推荐服务获取5个。实现此功能的实现三个接口:
基于callback模式实现上面功能代码如下:
userService.getFavorites(userId, new Callback>() { // ①
public void onSuccess(List list) {
if (list.isEmpty()) {
suggestionService.getSuggestions(new Callback>() {
public void onSuccess(List list) { // ③
UiUtils.submitOnUiThread(() -> {
list.stream()
.limit(5)
.forEach(uiList::show);
});
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
} else { // ②
list.stream()
.limit(5)
.forEach(favId -> favoriteService.getDetails(favId, new Callback() {
public void onSuccess(Favorite details) {
UiUtils.submitOnUiThread(() -> uiList.show(details));
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
}
));
}
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
上述使用了大量callback,这些代码逻辑晦涩难懂,且存在重复代码,下面我们使用RxJava配合Retrofit实现同样的需求:
userService.getFavorites(userId)
.flatMap(Observable::fromIterable)
.take(5)
.flatMap(favoriteService::getDetails)
//.storted()
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.observeOn(AndroidSchedules.MainThread)
.subscribe(uiList::show, UiUtils::errorPopup);
如上,RxJava的代码基于声明式编程,比较通俗易懂,代码量也比较少,不含有重复的代码。
在通过一个例子对比一下RxJava与Future的区别:
首先我们获取一个id列表,然后根据id分别获取对应的name和统计数据,然后组合每个id对应的name和统计数据为一个新的数据,最后输出所有组合对的值。使用CompletableFuture来实现这个功能,以便保证整个过程是异步的,并且每个id对应的处理是并发的:
CompletableFuture> ids = ifhIds(); // ①
CompletableFuture> result = ids.thenComposeAsync(l -> { // ②
Stream> zip =
l.stream().map(i -> { // ③
CompletableFuture nameTask = ifhName(i);
CompletableFuture statTask = ifhStat(i);
return nameTask.thenCombineAsync(statTask, (name, stat) ->
"Name " + name + " has stats " + stat);
});
List> combinationList =
zip.collect(Collectors.toList()); // ④
CompletableFuture[] combinationArray =
combinationList.toArray(new CompletableFuture[combinationList.size()]); // ⑤
CompletableFuture allDone = CompletableFuture.allOf(combinationArray); // ⑥
return allDone.thenApply(v ->
combinationList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())); // ⑦
});
List results = result.join(); // ⑧
用RxJava实现通用的需求:
Observable ids = ifhrIds();
Observable combinations = ids.flatMap(id -> {
return ifhrName(id).zipWith(ifhrStat(id),
(name, stat) -> "Name " + name + " has stats " + stat));
});
Observable> result = combinations.toList();
List results = result.blockingFirst();
如上代码使用RxJava相比使用CompletableFuture来说,更简洁,更通俗易懂。
RxJava作为一个响应式框架有以下特点
RxJava等响应式框架不仅仅是一个异步通信框架,更重要的意义在于将传统的同步的命令式的开发范式引导为一个异步的声明式的开发范式,这将非常有利于我们编写出更加高性能的应用。