深入理解 RxJava 的執行緒模型
ReactiveX是Reactive Extensions的縮寫,一般簡寫為Rx,最初是LINQ的一個擴充套件,由微軟的架構師Erik Meijer領導的團隊開發,在2012年11月開源,Rx是一個程式設計模型,目標是提供一致的程式設計介面,幫助開發者更方便的處理非同步資料流,Rx庫支援.NET、JavaScript和C++,Rx近幾年越來越流行了,現在已經支援幾乎全部的流行程式語言了,Rx的大部分語言庫由ReactiveX這個組織負責維護,比較流行的有RxJava/RxJS/Rx.NET,社群網站是 reactivex.io。
Netflix參考微軟的Reactive Extensions建立了Java的實現RxJava,主要是為了簡化伺服器端的併發。2013年二月份,Ben Christensen 和 Jafar Husain發在Netflix技術部落格的一篇文章第一次向世界展示了RxJava。
RxJava也在Android開發中得到廣泛的應用。
ReactiveX
An API for asynchronous programming with observable streams.
A combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming.
雖然RxJava是為非同步程式設計實現的庫,但是如果不清楚它的使用,或者錯誤地使用了它的執行緒排程,反而不能很好的利用它的非同步程式設計提到系統的處理速度。本文通過例項演示錯誤的RxJava的使用,解釋RxJava的執行緒排程模型,主要介紹Scheduler
、observeOn
和subscribeOn
的使用。
本文中的例子以併發傳送http request請求為基礎,通過效能檢驗RxJava的執行緒排程。
第一個例子,效能超好?
我們首先看第一個例子:
public static void testRxJavaWithoutBlocking(int count) throws Exception { CountDownLatch finishedLatch = new CountDownLatch(1); long t = System.nanoTime(); Observable.range(0, count).map(i -> { //System.out.println("A:" + Thread.currentThread().getName()); return 200; }).subscribe(statusCode -> { //System.out.println("B:" + Thread.currentThread().getName()); }, error -> { }, () -> { finishedLatch.countDown(); }); finishedLatch.await(); t = (System.nanoTime() - t) / 1000000; //ms System.out.println("RxJavaWithoutBlocking TPS: " + count * 1000 / t); }
這個例子是一個基本的RxJava的使用,利用Range建立一個Observable, subscriber處理接收的資料。因為整個邏輯沒有阻塞,程式執行起來很快,
輸出結果為:
RxJavaWithoutBlocking TPS: 7692307 。
2 加上業務的模擬,效能超差
上面的例子是一個理想化的程式,沒雨任何阻塞。我們模擬一下實際的應用,加上業務處理。
業務邏輯是傳送一個http的請求,httpserver是一個模擬器,針對每個請求有30毫秒的延遲。subscriber統計請求結果:
public static void testRxJavaWithBlocking(int count) throws Exception { URL url = new URL("http://127.0.0.1:8999/"); CountDownLatch finishedLatch = new CountDownLatch(1); long t = System.nanoTime(); Observable.range(0, count).map(i -> { try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); int responseCode = conn.getResponseCode(); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { //response.append(inputLine); } in.close(); return responseCode; } catch (Exception ex) { return -1; } }).subscribe(statusCode -> { }, error -> { }, () -> { finishedLatch.countDown(); }); finishedLatch.await(); t = (System.nanoTime() - t) / 1000000; //ms System.out.println("RxJavaWithBlocking TPS: " + count * 1000 / t); }
執行結果如下:
RxJavaWithBlocking TPS: 29。
@#¥%%……&!
效能怎麼突降呢,第一個例子看起來效能超好啊,http server只增加了一個30毫秒的延遲,導致這個方法每秒只能處理29個請求。
如果我們估算一下, 29*30= 870 毫秒,大約1秒,正好和單個執行緒傳送處理所有的請求的TPS差不多。
後面我們也會看到,實際的確是一個執行緒處理的,你可以在程式碼中加入
3 加上排程器,不起作用?
如果你對subscribeOn
和observeOn
方法有些印象的話,可能會嘗試使用排程器去解決:
public static void testRxJavaWithBlocking(int count) throws Exception { URL url = new URL("http://127.0.0.1:8999/"); CountDownLatch finishedLatch = new CountDownLatch(1); long t = System.nanoTime(); Observable.range(0, count).map(i -> { try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); int responseCode = conn.getResponseCode(); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { //response.append(inputLine); } in.close(); return responseCode; } catch (Exception ex) { return -1; } }).subscribeOn(Schedulers.io()).observeOn(Schedulers.computation()).subscribe(statusCode -> { }, error -> { }, () -> { finishedLatch.countDown(); }); finishedLatch.await(); t = (System.nanoTime() - t) / 1000000; //ms System.out.println("RxJavaWithBlocking TPS: " + count * 1000 / t); }
加上.subscribeOn(Schedulers.io()).observeOn(Schedulers.computation())
看一下效能:
RxJavaWithBlocking TPS: 30。
效能沒有改觀,是時候瞭解一下RxJava執行緒排程的問題了。
4 RxJava的執行緒模型
首先,依照Observable Contract, onNext
是順序執行的,不會同時由多個執行緒併發執行。
預設情況下,它是在呼叫subscribe方法的那個執行緒中執行的。如第一個例子和第二個例子,Rx的操作和訊息接收處理都是在同一個執行緒中執行的。一旦由阻塞,比如第二個例子,久會導致這個執行緒被阻塞,吞吐量下降。
但是subscribeOn
可以改變Observable的執行執行緒。
上圖中可以看到,如果你使用了subscribeOn
方法,則Rx的執行將會切換到另外的執行緒上,而不是預設的呼叫執行緒。
需要注意的是,如果在Observable鏈中呼叫了多個subscribeOn
方法,無論呼叫點在哪裡,Observable鏈只會使用第一個subscribeOn
指定的排程器,正所謂”一見傾情”。
但是onNext
還是順序執行的,所以第二個例子的效能依然低下。
observeOn
可以中途改變Observable鏈的執行緒。前面說了,subscribeOn
方法改變的源Observable的整個的執行執行緒,要想中途切換執行緒,就需要observeOn
方法。
官方的一個簡略晦澀的解釋如下:
The SubscribeOn operator changes this behavior by specifying a different Scheduler on which the Observable should operate. The ObserveOn operator specifies a different Scheduler that the Observable will use to send notifications to its observers.
一圖勝千言:
注意箭頭的顏色和橫軸的顏色,不同的顏色代表不同的執行緒。
5 Schedulers
上面我們瞭解了RxJava可以使用subscribeOn
和observeOn
可以改變和切換執行緒,以及onNext
是順序執行的,不是併發執行,至多也就切換到另外一個執行緒,如果它中間的操作是阻塞的,久會影響整個Rx的執行。
Rx是通過排程器來選擇哪個執行緒執行的,RxJava內建了幾種排程器,分別為不同的case提供執行緒:
- io() : 這個排程器時用於I/O操作, 它可以增長或縮減來確定執行緒池的大小它是使用CachedThreadScheduler來實現的。需要注意的是,它的執行緒池是無限制的,如果你使用了大量的執行緒的話,可能會導致OutOfMemory等資源用盡的異常。
- computation() : 這個是計算工作預設的排程器,它與I/O操作無關。它也是許多RxJava方法的預設排程器:buffer(),debounce(),delay(),interval(),sample(),skip()。
因為這些方法內部已經呼叫的排程器,所以你再呼叫subscribeOn
是無效的,比如下面的例子總是使用computation
排程器的執行緒。
Observable.just(1,2,3) .delay(1, TimeUnit.SECONDS) .subscribeOn(Schedulers.newThread()) .map(i -> { System.out.println("map: " + Thread.currentThread().getName()); return i; }) .subscribe(i -> {});
- immediate() :這個排程器允許你立即在當前執行緒執行你指定的工作。它是timeout(),timeInterval(),以及timestamp()方法預設的排程器。
- newThread() :建立一個新的執行緒只從。
- trampoline() :為當前執行緒建立一個佇列,將當前任務加入到佇列中依次執行。
同時,Schedulers
還提供了from
靜態方法,使用者可以定製執行緒池:
ExecutorService es = Executors.newFixedThreadPool(200, new ThreadFactoryBuilder().setNameFormat("SubscribeOn-%d").build()); Schedulers.from(es)
6 改造,非同步執行
現在,我們已經瞭解了RxJava的執行緒執行,以及相關的排程器。可以看到上面的例子還是順序阻塞執行的,即使是切換到另外的執行緒上,依然是順序阻塞執行,顯示它的吞吐率非常非常的低。下一步我們就要改造這個例子,讓它能非同步的執行。
下面是一種改造方案,我先把程式碼貼出來,再解釋:
public static void testRxJavaWithFlatMap(int count) throws Exception { ExecutorService es = Executors.newFixedThreadPool(200, new ThreadFactoryBuilder().setNameFormat("SubscribeOn-%d").build()); URL url = new URL("http://127.0.0.1:8999/"); CountDownLatch finishedLatch = new CountDownLatch(1); long t = System.nanoTime(); Observable.range(0, count).subscribeOn(Schedulers.io()).flatMap(i -> { //System.out.println("A: " + Thread.currentThread().getName()); return Observable.just(i).subscribeOn(Schedulers.from(es)).map(v -> { //System.out.println("B: " + Thread.currentThread().getName()); try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); int responseCode = conn.getResponseCode(); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { //response.append(inputLine); } in.close(); return responseCode; } catch (Exception ex) { return -1; } } ); } ).observeOn(Schedulers.computation()).subscribe(statusCode -> { //System.out.println("C: " + Thread.currentThread().getName()); }, error -> { }, () -> { finishedLatch.countDown(); }); finishedLatch.await(); t = (System.nanoTime() - t) / 1000000; //ms System.out.println("RxJavaWithFlatMap TPS: " + count * 1000 / t); es.shutdownNow(); }
通過flatmap
可以將源Observable的元素項轉成n個Observable,生成的每個Observable可以使用執行緒池併發的執行,同時flatmap還會將這n個Observable merge成一個Observable。你可以將其中的註釋開啟,看看執行緒的執行情況。
效能還不錯:
RxJavaWithFlatMap TPS: 3906。
FlatMap — transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
7 另一種解決方案
我們已經清楚了要並行執行提高吞吐率的解決辦法就是建立多個Observable並且併發執行。基於這種解決方案,我們還可以有其它的解決方案。
上一方案中利用flatmap建立多個Observable,針對我們的例子,我們何不直接建立多個Observable呢?
public static void testRxJavaWithParallel(int count) throws Exception { ExecutorService es = Executors.newFixedThreadPool(200, new ThreadFactoryBuilder().setNameFormat("SubscribeOn-%d").build()); URL url = new URL("http://127.0.0.1:8999/"); CountDownLatch finishedLatch = new CountDownLatch(count); long t = System.nanoTime(); for (int k = 0; k < count; k++) { Observable.just(k).map(i -> { //System.out.println("A: " + Thread.currentThread().getName()); try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); int responseCode = conn.getResponseCode(); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { //response.append(inputLine); } in.close(); return responseCode; } catch (Exception ex) { return -1; } }).subscribeOn(Schedulers.from(es)).observeOn(Schedulers.computation()).subscribe(statusCode -> { }, error -> { }, () -> { finishedLatch.countDown(); }); } finishedLatch.await(); t = (System.nanoTime() - t) / 1000000; //ms System.out.println("RxJavaWithParallel TPS: " + count * 1000 / t); es.shutdownNow(); }
效能更好一點:
RxJavaWithParallel2 TPS: 4716。
這個例子沒有使用Schedulers.io()
作為它的排程器,這是因為如果在大併發的情況下,可能會出現建立過多的執行緒導致資源不錯,所以我們限定使用200個執行緒。
8 總結
- subscribeOn() 改變的Observable執行(operate)使用的排程器,多次呼叫無效。
- observeOn() 改變Observable傳送notifications的排程器,會影響後續的操作,可以多次呼叫
- 預設情況下, 操作鏈使用的執行緒是呼叫
subscribe()
的執行緒 Schedulers
提供了多個排程器,可以並行執行多個Observable- 使用RxJava可以實現非同步程式設計,但是依然要小心執行緒阻塞。而且由於這種非同步的程式設計,除錯程式碼可能更加的困難
相關文章
- 理解 RxJava 執行緒模型RxJava執行緒模型
- RxJava 執行緒模型分析RxJava執行緒模型
- 深入理解多執行緒(二)—— Java的物件模型執行緒Java物件模型
- 深入理解JVM(③)執行緒與Java的執行緒JVM執行緒Java
- 深入理解執行緒池的執行流程執行緒
- 深入理解JavaScript執行(單執行緒的JS)JavaScript執行緒JS
- 深入學習redis 的執行緒模型Redis執行緒模型
- 深入理解Flutter多執行緒Flutter執行緒
- 深入理解執行緒通訊執行緒
- RxJava 中的多執行緒RxJava執行緒
- 理解微信小程式的雙執行緒模型微信小程式執行緒模型
- 【高併發】深入理解執行緒的執行順序執行緒
- 深入理解Java之執行緒池Java執行緒
- Android執行緒篇(四)深入理解Java執行緒池(二)Android執行緒Java
- Android執行緒篇(三)深入理解Java執行緒池(一)Android執行緒Java
- 深入理解JVM(③)再談執行緒安全JVM執行緒
- 深入Java原始碼理解執行緒池原理Java原始碼執行緒
- 深入理解執行緒池原理篇執行緒
- 深入理解 OpenMP 執行緒同步機制執行緒
- 深入理解多執行緒程式設計執行緒程式設計
- 深入理解多執行緒(三)—— Java的物件頭執行緒Java物件
- Java執行緒中斷的本質深入理解Java執行緒
- 以 DEBUG 方式深入理解執行緒的底層執行原理執行緒
- 深入理解Java多執行緒與併發框(第①篇)——執行緒的狀態Java執行緒
- 執行緒模型執行緒模型
- 深入理解Javascript單執行緒談Event LoopJavaScript執行緒OOP
- [深入理解Java虛擬機器]執行緒Java虛擬機執行緒
- 深入理解多執行緒(一)——Synchronized的實現原理執行緒synchronized
- Redis的執行緒模型Redis執行緒模型
- Dubbo的執行緒模型執行緒模型
- memcache的執行緒模型執行緒模型
- 深入理解 Java 多執行緒、Lambda 表示式及執行緒安全最佳實踐Java執行緒
- [深入理解Java虛擬機器]第十二章 Java記憶體模型與執行緒-Java與執行緒Java虛擬機記憶體模型執行緒
- 深入執行緒同步執行緒
- Java語言深入 多執行緒程式模型研究(轉)Java執行緒模型
- 深入理解Java多執行緒與併發框(第⑧篇)——深入理解:CASJava執行緒
- Java核心(二)深入理解執行緒池ThreadPoolJava執行緒thread
- 作業系統——深入理解程式和執行緒作業系統執行緒