之前曾寫過(guò)一篇介紹 Spring 提出的 Reactive Streams 實(shí)現(xiàn)方案 —— Project Reactor 的文章:《Reactor 入門與實(shí)踐》,其中介紹了 Spring Reactor API 的用法。相信很多人都能發(fā)現(xiàn),Spring Reactor 的 API 看上去和 Java 8 Stream 很像:
Project Reactor:
Flux.fromIterable(Character.captainAmerica3())
.filter(Character::isPlay)
.map(Character::getName)
.subscribe(System.out::println);
Java 8 Stream:
Character.captainAmerica3().stream()
.filter(Character::isPlay)
.map(Character::getName)
.forEach(System.out::println);
上面兩段代碼分別由 Spring Reactor 和 Java 8 Stream 實(shí)現(xiàn),功能是打印《美隊(duì)3》參演的角色名稱。代碼的形式和大部分方法名稱都是相同的。
那 Spring Reactor 或者說(shuō) Reactive Streams 和 Java 8 Stream 的差別是什么呢?
簡(jiǎn)單來(lái)說(shuō),這兩者最大的差別是前者是 Push-based,后者是 Pull-based。
Push 與 Pull
Reactive Streams 與 Java 8 Stream 最大的不同在于,前者是 Push 模式,后者是 Pull 模式??赡艽蟛糠秩藢?duì)推與拉的概念不是很了解。先做一個(gè)形象的介紹,常見(jiàn)的操作集合類的方式就是 Pull 模式:
List<User> users = userRpcClient.findAllUsers();
for (String user : users) {
// do something
}
為什么說(shuō)這樣的代碼是 Pull 模式呢?因?yàn)檫@樣的代碼,客戶端主動(dòng)向服務(wù)端請(qǐng)求數(shù)據(jù),然后在操作數(shù)據(jù)。好比客戶端主動(dòng)從服務(wù)端拉取數(shù)據(jù),故稱為拉模式。
那 Push 模式是什么樣呢?
userRpcPublisher.subscribe(new UserSubscriber() {
public void onNext(User user) {
// do something
}
});
簡(jiǎn)單來(lái)說(shuō)就是 Callback 風(fēng)格的代碼通常都是 Push 模式的。
Pull 模式的代碼的問(wèn)題在于,如果
userRpcClient.findAllUsers()
表示的操作是一個(gè)很耗時(shí)的操作,那 Pull-based 的代碼的并發(fā)能力將很成問(wèn)題。這就是 Push 模式出現(xiàn)的原因。
當(dāng)然,上面兩個(gè)例子只是為了給大家一個(gè)直觀的介紹。并不是說(shuō)傳統(tǒng)形式的代碼就一定是 Pull 模式,Callback 風(fēng)格的代碼就一定是 Pull 模式。
換言之,代碼形式并不是 Pull 與 Push 的本質(zhì)。從更深的層面說(shuō),Pull 模式對(duì)應(yīng)的是同步的、命令式的程序,Push 模式對(duì)應(yīng)的是異步的、非阻塞的、反應(yīng)式的程序。
因此,雖然在代碼形式上說(shuō) Java 8 Stream 和 Reactive Streams 的代碼有些像,但從本質(zhì)上來(lái)說(shuō),同步、阻塞的 Java 8 Stream 與異步、非阻塞的 Reactive Streams 有著很大的差別。
因此 Reactive Streams 不僅在形的層面,以接口定義的形式對(duì)反應(yīng)式編程做出了規(guī)范,更在實(shí)的層面定義了 TCK,用來(lái)保證相關(guān)實(shí)現(xiàn)確實(shí)滿足了異步、非阻塞等等的要求。