前言
自從去年8月底《淺談RxJava與2.0的新特性》,已經過去快一年。筆者也沒想到此文竟有讀者等筆者填坑快一年了,不禁汗顏。所以筆者打算寫關于一個 RxJava2 的系列文章,既為填坑,也為回報讀者對我的支持。本文為第一篇。
讀本系列文章,你可能有如下收獲:
- 了解其設計原理,代碼實現
- 掌握操作符的正確使用姿勢,避免采坑
- 強化 Rx 編程思想,寫出更 Rx 的代碼
- 跟讀精彩的源碼,強化編程功底
廢話不多說,進入正題。
Reactive Streams
之前在《淺談RxJava與2.0的新特性》我們提到過, RxJava2 遵循 Reactive Streams 的編程規范, 或者更精確的說,是 RxJava2 中的 Flowable 相關的類。因此我們只分析 Flowable 相關的實現與使用,剩下的 Observable、 Completable、Single、 Maybe
這些不會再提及,相信讀者朋友們可以舉一反三。
Reactive Streams 中明確規范了如下4點:
- process a potentially unbounded number of elements
- in sequence,
- asynchronously passing elements between components,
- with mandatory non-blocking backpressure.
后面筆者會用 RS 代替全稱。
請跟隨本系列文章慢慢看 Flowable 是出色的完成上述的要求。
閱讀源碼的正確姿勢
Rx2 在源碼中加入了一些注解,這些注解對運行沒有任何實際作用,僅僅是用作標識備注,有助于開發者了解某個操作符的正確使用姿勢,同時也有利于閱讀源碼時整理思路。這些注解位于io.reactivex.annotations
包名下,這里著重介紹一個。
BackpressureSupport
BackpressureSupport 是用作標識這個操作符對背壓的支持類型,有以下幾種:
- PASS_THROUGH:表示這個操作符僅僅傳遞背壓類型,不做任何改變。例如
defer
操作符,這個操作符支持的背壓類型取決于Callable
產生的Publisher
。 - FULL:表示這個操作符支持完全的背壓,協調上下游關系
- SPECIAL:表示這個操作符支持的背壓類型由方法上的文檔說明
- UNBOUNDED_IN:表示這個操作符會向上游請求 Long.MAX_VALUE,并協調下游
- ERROR:表示如果下游沒有足夠數量的request,上游發射了超額的數據,這個操作符會拋出一個
MissingBackpressureException
- NONE:表示不處理背壓
上面這些字面解釋看起來還是很繞的,尤其是對于沒有閱讀過相關源碼的讀者。我們也不必一次性全部弄明白,后續會慢慢講清楚所有。
走進create源碼
前文中有提到過,Rx2 收回了create
方法的權限,使開發者自定義的create
也能夠正確的支持背壓。而實現的方式就是通過額外提供一個BackpressureStrategy
參數。也因此,create
的方法注解中 BackpressureSupport
是 SPECIAL
。
FlowableCreate
拋開Rx2提供的 plugin 不談,本質上就是用 create 傳進來的2個參數創建了FlowableCreate
這個類。
根據傳入的BackpressureStrategy
生成不同的Emitter
對象。并遵循一致的編程約定,先調用 onSubscribe,隨后將Emitter
傳遞給FlowableOnSubscribe
用來發射數據。
@Override
public void subscribeActual(Subscriber<? super T> t) {
BaseEmitter<T> emitter;
switch (backpressure) {
case MISSING: {
emitter = new MissingEmitter<T>(t);
break;
}
case ERROR: {
emitter = new ErrorAsyncEmitter<T>(t);
break;
}
case DROP: {
emitter = new DropAsyncEmitter<T>(t);
break;
}
case LATEST: {
emitter = new LatestAsyncEmitter<T>(t);
break;
}
default: {
emitter = new BufferAsyncEmitter<T>(t, bufferSize());
break;
}
}
t.onSubscribe(emitter);
try {
source.subscribe(emitter);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
emitter.onError(ex);
}
}
BackpressureStrategy
Rx2 大量的類通過繼承AtomicLong
來表示計算個數與發射個數,請求一個+1,發射一個-1。并且在 Rx2 中 Long.MAX_VALUE 有特殊含義,表示無限的數據。即,如果 request(Long.MAX_VALUE)
,即使發射了數據也不會減少自身的數值。
這里所有的 Emitter 都繼承了基類BaseEmitter
,并提供一些公共方法如setDisposable/setCancellable/requested/serialize
等。然后根據各自的背壓策略,實現相應的邏輯,下面分別介紹。
MISSING
MISSING即沒有背壓,我們看 onNext 函數會發現,每調用一次就會傳遞給下游的 subscriber.onNext
,空指針則onError
。
@Override
public void onNext(T t) {
if (isCancelled()) {
return;
}
if (t != null) {
actual.onNext(t);
} else {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
for (;;) {
long r = get();
if (r == 0L || compareAndSet(r, r - 1)) {
return;
}
}
}
上面的代碼是2.1.2版本的源碼,筆者認為這里有一處 BUG 。即在自減的時候,沒有檢查 Long.MAX_VALUE 的情況,導致在request(Long.MAX_VALUE)
后,發射數據時依然會不斷自減,這是與一致的設計思路相悖的。反觀下面 DROP 與 BUFFER 相關的 Emitter 處理時,則直接調用了BackpressureHelper.produced(this, 1)
,在里面會有 Long.MAX_VALUE 的判斷。
雖然有點小 BUG,但是實際中除了在requested()
函數中會出錯外,不會影響正常的執行流。且一般開發者也不會使用Emitter.requested()
函數。
雖然 MISSING 不支持背壓,但是沒關系,我們可以通過操作符來彌補。
這些操作符結合使用 MISSING 的 create 方法,使得原本不支持背壓的 Flowable 支持背壓。
當然我們也大可不必這樣麻煩,既然要使用 buffer、drop 或者 latest,使用下面的策略即可。除非我們需要那些操作符提供的額外功能。
ERROR
ERROR則和最開始的BackpressureSupport.ERROR
表現一致。
下面這塊代碼是NoOverflowBaseAsyncEmitter
,會被 ERROR 和 DROP 對應的Emitter
繼承。邏輯也很簡單,即請求數如果還大于0,則向下發射并將請求數減1,否則走onOverflow()
方法。
@Override
public final void onNext(T t) {
if (isCancelled()) {
return;
}
if (t == null) {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
if (get() != 0) {
actual.onNext(t);
BackpressureHelper.produced(this, 1);
} else {
onOverflow();
}
}
而 ERROR 對應的實現則很簡單了,不在贅述。
@Override
void onOverflow() {
onError(new MissingBackpressureException("create: could not emit value due to lack of requests"));
}
DROP
DROP即直接丟棄超額的數據,體現在代碼中就非常簡單。
@Override
void onOverflow() {
// nothing to do
}
BUFFER 與 LATEST
這倆之所以放到了一起,是因為 BUFFER 與 LATEST 本質上都是緩存了數據,細節上的區別就是,BUFFER 是緩存了所有數據,而 LATEST 只保留了最近的一個 onNext 數據。
體現在代碼中這兩者最主要的區別就是一個用了隊列來緩存,一個用了AtomicReference 來維持最后一個未被消費的數據。
就挑 BUFFER 來說, onNext 就是將數據扔進隊列,而后嘗試消費數據即調用drain()
。onError 與 onComplete 則是將 結束標識置為 true ,并保留異常,然后依然也是在drain()
中消費該消息。
在/onNext/onError/onComplete/onRequested時,都會調用drain()
來消費隊列中的數據。
@Override
public void onNext(T t) {
if (done || isCancelled()) {
return;
}
if (t == null) {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
queue.offer(t);
drain();
}
drain
中做的事就比較復雜了,為了保證線程安全,首先通過一個AtomicInteger
來確保只有一個線程可以進入for(;;)
循環。
在 for 循環中,不斷的消費隊列中的數據,如果隊列為空則檢查結束標識是否為 true,是的話則發射 onComplete 或者 onError 。
void drain() {
if (wip.getAndIncrement() != 0) {
return;
}
int missed = 1;
final Subscriber<? super T> a = actual;
final SpscLinkedArrayQueue<T> q = queue;
for (;;) {
long r = get();
long e = 0L;
while (e != r) {
if (isCancelled()) {
q.clear();
return;
}
boolean d = done;
T o = q.poll();
boolean empty = o == null;
if (d && empty) {
Throwable ex = error;
if (ex != null) {
error(ex);
} else {
complete();
}
return;
}
if (empty) {
break;
}
a.onNext(o);
e++;
}
if (e == r) {
if (isCancelled()) {
q.clear();
return;
}
boolean d = done;
boolean empty = q.isEmpty();
if (d && empty) {
Throwable ex = error;
if (ex != null) {
error(ex);
} else {
complete();
}
return;
}
}
if (e != 0) {
BackpressureHelper.produced(this, e);
}
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
這里請大家留意一個編程的套路,在絕大多數隊列消費的場景里, Rx2 中都是使用了下面的方式。這也是我們可以積累使用的。通過這種方式可以保證for循環里的代碼是單線程執行的,且如果執行期間有一次或多次新的調用drain()
,會導致重新走一遍包含注釋處的代碼,確保數據可以正確的消費發射。
void drain() {
if (wip.getAndIncrement() != 0) {
return;
}
int missed = 1;
for (;;) {
// 消費隊列, 發射數據
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
小結
筆者在介紹過程中已經省略了很多細枝末節,不免顯得知識有些分散,結合源碼閱讀效果更佳。沒想到一個小小的 create 也包含這么多的玄機。
我相信通過閱讀這篇文章,讀者們寫 create 的時候應該可以做到結合實際場景選擇正確的BackpressureStrategy
。
有了 create 便從此開啟 Rx2 萬里長征第一步。下一篇,我們將會介紹 Rx2 的線程調度相關的操作符及其實現,敬請期待。