本文作者JasonChen,原文地址: chblog.me/2018/12/19/…
前一篇文章我們講述到RxJava2 的內部設計模式與原理機制,包括觀察者模式和裝飾者模式,其本質上都是RxJava2的事件驅動,那麼本篇文章將會講到RxJava2 的另外一個重要功能:非同步。
RxJava2 深入解析
依舊是從原始碼實現開始,帶著疑惑去讀,前一篇文章我們講到subcribeOn方法內部的實現涉及執行緒池:Scheduler.Worker w = scheduler.createWorker()
這邊涉及兩個重要元件:
- scheduler排程器
- 自定義執行緒池
scheduler排程器原始碼解析
public final class Schedulers {
@NonNull
static final Scheduler SINGLE;
@NonNull
static final Scheduler COMPUTATION;
@NonNull
static final Scheduler IO;
@NonNull
static final Scheduler TRAMPOLINE;
@NonNull
static final Scheduler NEW_THREAD;
複製程式碼
一共有如下的五種排程器,分別對應不同的場景,當然企業可以針對自身的場景設定自己的排程器。
- SINGLE,針對單一任務設定的單個定時執行緒池
- COMPUTATION,針對計算任務設定的定時執行緒池的資源池(陣列)
- IO,針對IO任務設定的單個可複用的定時執行緒池
- TRAMPOLINE,trampoline翻譯是蹦床(佩服作者的腦洞)。這個排程器的原始碼註釋是:任務在當前執行緒工作(不是執行緒池)但是不會立即執行,任務會被放入佇列並在當前的任務完成之後執行。簡單點說其實就是入隊然後慢慢線性執行(這裡巧妙的方法其實和前面我們所講的回壓實現機制基本是一致的,值得借鑑)
- NEW_THREAD,單個的週期執行緒池和single基本一致唯一不同的是single對thread進行了一個簡單的NonBlocking封裝,這個封裝從原始碼來看基本沒有作用,只是一個marker interface標誌介面
computation排程器原始碼分析
computation排程器針對大量計算場景,在後端併發場景會更多的用到,那麼其是如何實現的呢?接下來帶著疑惑進行原始碼分析。
public final class ComputationScheduler extends Scheduler implements SchedulerMultiWorkerSupport {
// 資源池
final AtomicReference<FixedSchedulerPool> pool;
// 這是computationScheduler類中實現的createWork()方法
public Worker createWorker() {
// 建立EventLoop工作者,入參是一個PoolWorker
return new EventLoopWorker(pool.get().getEventLoop());
}
static final class FixedSchedulerPool implements SchedulerMultiWorkerSupport {
final int cores;
// 資源池工作者,每個工作者其實都是一個定時執行緒池
final PoolWorker[] eventLoops;
long n;
// 對應前面的函式呼叫
public PoolWorker getEventLoop() {
int c = cores;
if (c == 0) {
return SHUTDOWN_WORKER;
}
// simple round robin, improvements to come
// 這裡其實就是從工作者陣列中輪詢選出一個工作者
這裡其實擁有提升和優化的空間,這裡筆者可能會向開源社群提交一個pr
以此進行比較好的排程器排程
return eventLoops[(int)(n++ % c)];
}
// 此處是一個簡單的封裝
static final class PoolWorker extends NewThreadWorker {
PoolWorker(ThreadFactory threadFactory) {
super(threadFactory);
}
}
public class NewThreadWorker extends Scheduler.Worker implements Disposable {
private final ScheduledExecutorService executor;
volatile boolean disposed;
public NewThreadWorker(ThreadFactory threadFactory) {
// 進行定時執行緒池的初始化
executor = SchedulerPoolFactory.create(threadFactory);
}
public static ScheduledExecutorService create(ThreadFactory factory) {
final ScheduledExecutorService exec =
// 初始化一個定時執行緒池
Executors.newScheduledThreadPool(1, factory);
tryPutIntoPool(PURGE_ENABLED, exec);
return exec;
}
複製程式碼
上述程式碼清晰的展示了computation排程器的實現細節,這裡需要說明的是定時執行緒池的core設定為1,執行緒池的個數最多為cpu數量,這裡涉及到ScheduledThreadPoolExecutor定時執行緒池的原理,簡單的說起內部是一個可自動增長的陣列(佇列)類似於ArrayList,也就是說佇列永遠不會滿,執行緒池中的執行緒數不會增加。
接下來結合訂閱執行緒和釋出執行緒分析其之間如何進行溝通的本質。
釋出執行緒在上一篇的文章已經提到,內部是一個worker,那麼訂閱執行緒也是麼,很顯然必須是的,接下來我們來看下原始碼:
// 還是從subscribeActul開始(原因見上一篇文章)
public void subscribeActual(Subscriber<? super T> s) {
Worker worker = scheduler.createWorker();
if (s instanceof ConditionalSubscriber) {
source.subscribe(new ObserveOnConditionalSubscriber<T>(
(ConditionalSubscriber<? super T>) s, worker, delayError, prefetch));
} else {
//
source.subscribe(new ObserveOnSubscriber<T>(s, worker, delayError, prefetch));
}
}
複製程式碼
其內部封裝了一個ObserveOnsubcriber
,這是個對下流訂閱者的封裝,主要什麼作用呢,為什麼要這個呢?其實這個涉及訂閱執行緒內部的機制,接著看原始碼瞭解其內部機制。
// 基類
abstract static class BaseObserveOnSubscriber<T> extends BasicIntQueueSubscription<T>
implements FlowableSubscriber<T>, Runnable {
private static final long serialVersionUID = -8241002408341274697L;
final Worker worker;
final boolean delayError;
final int prefetch;
//...
@Override
public final void onNext(T t) {
if (done) {
return;
}
if (sourceMode == ASYNC) {
trySchedule();
return;
}
if (!queue.offer(t)) {
upstream.cancel();
error = new MissingBackpressureException("Queue is full?!");
done = true;
}
// 開啟訂閱者執行緒池模式的排程,具體實現在子類中實現
trySchedule();
}
@Override
public final void onError(Throwable t) {
if (done) {
RxJavaPlugins.onError(t);
return;
}
error = t;
done = true;
trySchedule();
}
@Override
public final void onComplete() {
if (!done) {
done = true;
trySchedule();
}
}
// 這裡並沒有向上傳遞request請求,而是把自己當做資料發射者進行request計數
@Override
public final void request(long n) {
if (SubscriptionHelper.validate(n)) {
BackpressureHelper.add(requested, n);
// 開啟排程
trySchedule();
}
}
// 排程程式碼
final void trySchedule() {
// 上一篇文章講過這個的用法
if (getAndIncrement() != 0) {
return;
}
// 啟用一個work來進行任務的執行 this物件說明實現了runable介面
worker.schedule(this);
}
// 排程實現的程式碼
@Override
public final void run() {
if (outputFused) {
runBackfused();
} else if (sourceMode == SYNC) {
runSync();
} else {
// 一般會呼叫runAsync方法
runAsync();
}
}
abstract void runBackfused();
abstract void runSync();
abstract void runAsync();
//...
}
複製程式碼
當上遊的裝飾者(上一篇提到的裝飾者模式)呼叫onNext方法時,這時並沒有類似的去呼叫下游的onNext方法,那這個時候其實就是訂閱者執行緒模式的核心原理:採用queue佇列進行資料的store,這裡嘗試將資料放進佇列。
ObserveOnSubscriber的具體實現類部分實現如下。
static final class ObserveOnSubscriber<T> extends BaseObserveOnSubscriber<T>
implements FlowableSubscriber<T> {
private static final long serialVersionUID = -4547113800637756442L;
final Subscriber<? super T> downstream;
ObserveOnSubscriber(
Subscriber<? super T> actual,
Worker worker,
boolean delayError,
int prefetch) {
super(worker, delayError, prefetch);
this.downstream = actual;
}
//這是上游回撥這個subscriber時呼叫的方法,詳情見上一篇文章
@Override
public void onSubscribe(Subscription s) {
if (SubscriptionHelper.validate(this.upstream, s)) {
this.upstream = s;
if (s instanceof QueueSubscription) {
@SuppressWarnings("unchecked")
QueueSubscription<T> f = (QueueSubscription<T>) s;
int m = f.requestFusion(ANY | BOUNDARY);
if (m == SYNC) {
sourceMode = SYNC;
queue = f;
done = true;
downstream.onSubscribe(this);
return;
} else
if (m == ASYNC) {
sourceMode = ASYNC;
queue = f;
downstream.onSubscribe(this);
s.request(prefetch);
return;
}
}
// 設定快取佇列
// 這裡涉及一個特別之處就是預獲取(提前獲取資料)
queue = new SpscArrayQueue<T>(prefetch);
// 觸發下游subscriber 如果有request則會觸發下游對上游資料的request
downstream.onSubscribe(this);
// 請求上游資料 上面的程式碼和這行程式碼就是起到承上啟下的一個作用,也就是預獲取,放在佇列中
s.request(prefetch);
}
}
//...
複製程式碼
下面看一下抽象方法runAsync()
的實現。
@Override
void runAsync() {
int missed = 1;
final Subscriber<? super T> a = downstream;
final SimpleQueue<T> q = queue;
long e = produced;
for (;;) {
long r = requested.get();
while (e != r) {
boolean d = done;
T v;
try {
// 獲取資料
v = q.poll();
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
cancelled = true;
upstream.cancel();
q.clear();
a.onError(ex);
worker.dispose();
return;
}
boolean empty = v == null;
if (checkTerminated(d, empty, a)) {
return;
}
if (empty) {
break;
}
a.onNext(v);
e++;
// limit = prefetch - (prefetch >> 2)
// prefetch = BUFFER_SIZE(上一篇文章提到的預設128)
if (e == limit) {
if (r != Long.MAX_VALUE) {
r = requested.addAndGet(-e);
}
upstream.request(e);
e = 0L;
}
}
if (e == r && checkTerminated(done, q.isEmpty(), a)) {
return;
}
// 下面的程式碼機制在上一篇講過主要涉及非同步程式設計技巧
int w = get();
if (missed == w) {
produced = e;
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
} else {
missed = w;
}
}
}
//...
}
複製程式碼
前面說過,訂閱者把自己當成一個發射者,那數/據從哪裡來呢,而且還要持續有資料,那麼後面的程式碼說明了資料來源,當資料達到limit,開始新的資料的prefetch,每次preftch的數量是limit。
為何要將訂閱者這樣區別設定呢,其實原因很簡單,訂閱者和釋出者需要不同的執行緒機制非同步地執行,比如訂閱者需要computation的執行緒機制來進行大量的耗時資料計算,但又要保持一致的裝修者模式,所以原始碼的做法是訂閱者這邊打破回撥的呼叫流,採用資料佇列進行兩個執行緒池之間的資料傳送。
本文總結
筆者喜歡總結,總結意味著我們反思和學習前面的知識點,應用點以及自身的不足。
- rxjava2執行緒排程的原理機制,不同場景下執行緒機制需要進行定製
- rxjava2生產和消費的非同步原理和實現方式