文章目錄
1. Strem 組件結構
1.1 操作分類
根據Java 8 Stream(1)-流的使用可以知道,Stream
的操作可以分爲兩大類:中間操作與終結操作。中間操作只是對操作進行了記錄,終結操作纔會實際觸發計算邏輯(即惰性求值),源碼中也把 Stream 的一個操作稱爲一個 stage
- 中間操作
又可以分爲無狀態(Stateless)操作
與有狀態(Stateful)操作
,前者是指元素的處理不受之前元素的影響,可以一個一個即時處理;後者是指該操作只有拿到所有元素之後才能繼續下去,比如排序是有狀態操作,在讀取所有元素之前並不能確定排序結果- 終結操作
又可以分爲短路操作
與非短路操作
,前者是指遇到某些符合條件的元素就可以得到最終結果;而後者是指必須處理所有元素才能得到最終結果
以下繼承結構以
ReferencePipeline
爲例,其對應的Stream
類型爲StreamShape.REFERENCE
,也就是處理引用類型數據的流
1.2 操作對象的結構
target.stream()
.filter(fund -> fund.getConfirm()> 2)
.map(fund->String.valueOf(fund.getMode()))
.forEach(System.out::println);
以上代碼其對應的操作執行如下圖所示,其主要分爲兩個步驟:
Stream
被構造處理的時候首先獲得一個Head
對象,這是整個流操作執行鏈的頭節點。它也是一個操作行爲的封裝,只不過比較特殊,深度depth = 0
,且沒有對數據的操作邏輯,其主要的作用是串起整個流處理流程。當中間操作都執行完,則獲得了一條對每一步操作都進行了描述的 Stream 中間操作雙向鏈表,頭指針指到了stage2
- 當終結操作觸發時,以終結操作本身的數據處理邏輯的封裝對象
Sink0
爲起點,從操作鏈表尾部stage2
逆向遍歷,將操作動作中封裝的數據處理邏輯封裝成ChaineReference
對象,並將傳入的上一個Sink 引用
賦值給新建 Sink 的 downStream 變量,從而形成單向的調用鏈,頭指針指到了Sink2
1.2.1 流中間操作鏈表頭對象 Head
以 ReferencePipeline.Head
內部對象爲例,追溯其構造方法,最終抵達 AbstractPipeline
的構造方法。這其實就是 Stream 中間操作
對應的抽象節點,每一箇中間操作都被封裝成這樣一個節點,然後通過前後指針連接起來,形成了一條雙向鏈表。 從代碼看其比較重要的屬性如下:
previousStage
當前中間操作節點的上一個節點,因爲Head
爲整個雙向鏈表最上游,故其前一個節點爲 nullsourceSpliterator
數據源的可分解迭代器,並行流中分解任務所需sourceStage
保存的Head
頭節點引用,用於獲取保存在頭節點關於整個 Stream 處理流程中的關鍵信息,如是否是並行模式depth
當前節點的深度,Head
頭節點深度爲 0,該值在並行流大任務fork()分解子任務時可用於維護任務層級parallel
是否是並行模式,決定了是否啓用ForkJoinPool
用於並行執行任務
AbstractPipeline(Spliterator<?> source,
int sourceFlags, boolean parallel) {
this.previousStage = null;
this.sourceSpliterator = source;
this.sourceStage = this;
this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
// The following is an optimization of:
// StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
this.depth = 0;
this.parallel = parallel;
}
1.2.2 流中間操作的其他對象
以 ReferencePipeline#map()
對應的中間操作爲例,其代碼如下。可以看到其主要邏輯是生成一個新的 StatelessOp 對象,並且重寫了opWrapSink()
方法
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
StatelessOp
的構造方法也會追溯到 AbstractPipeline
的構造方法,簡單地說其主要完成以下幾件事:
- 將調用方對象自身作爲參數傳入,成爲這個新的操作對象的
previousStage
,執行previousStage.nextStage = this
將前一個操作對象的後指針指向這個新創建的操作對象,形成雙向鏈表結構- 如果 Stream 中存在有狀態的中間操作,
sourceStage.sourceAnyStateful = true
將其保存在 Head 頭節點中this.depth = previousStage.depth + 1
新創建的操作對象深度爲 previousStage 的 depth 加 1
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
if (previousStage.linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
previousStage.linkedOrConsumed = true;
previousStage.nextStage = this;
this.previousStage = previousStage;
this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
this.sourceStage = previousStage.sourceStage;
if (opIsStateful())
sourceStage.sourceAnyStateful = true;
this.depth = previousStage.depth + 1;
}
1.2.3 數據處理邏輯的封裝對象 Sink
以ReferencePipeline#map()
爲例,新的 StatelessOp
對象生成時重寫了opWrapSink()
方法,該方法會返回一個 Sink
對象。我們已經知道了 Stream 操作是如何記錄下來形成雙向鏈表的,但是這些操作中封裝的回調方法(也就是我們寫的對數據源的處理邏輯)的真正執行卻需要藉助 Sink
。Sink
接口爲各個操作的數據處理邏輯的調用提供了規範,其包含的方法如下所示:
// 開始遍歷元素之前調用該方法,通知Sink做好準備
default void begin(long size)
// 所有元素遍歷完成之後調用,通知Sink沒有更多的元素了
default void end()
// 是否可以結束操作,可以讓短路操作儘早結束
default boolean cancellationRequested()
// 遍歷元素時調用,接受一個待處理元素,並對元素進行處理
// Stage 把自己包含的回調方法封裝到該方法裏,前一個Stage只需要調用當前 Stage.accept(T t)方法就行了
default void accept(int value)
有了規範,每個
操作對象
都將自己的數據處理邏輯封裝到一個Sink
中,前一個操作只需調用後一個操作的Sink
接口方法即可,不需要知道其內部是如何處理的。事實上Stream
各個操作內部實現的本質,就是如何重載Sink
的四個接口方法,各操作實現自己的邏輯,處理數據時只需要從Head
開始對數據源依次調用每個操作對象對應的Sink.{begin(),accept(),cancellationRequested(),end()}
方法就可以了
- 對於有狀態的操作,
Sink
的begin()
和end()
方法是必須實現的。例如Stream.sorted()
,其操作對象封裝的數據處理邏輯 RefSortingSink 對象中,begin()
方法創建了一個存儲元素的容器,accept()
方法負責將元素添加到該容器,最後end()
實現對容器中的元素進行排序,並決定了下游 Sink 如何執行- 對於短路操作,Sink 的
cancellationRequested()
方法也是必須實現的,比如Stream.findFirst()
是短路操作,只要找到一個元素cancellationRequested()
就要返回true
,以便儘快結束查找
Sink
的繼承結構如下,其中 BooleanTerminalSink
爲 Match 操作的數據處理邏輯抽象,AccumulationgSink
爲 Reduce 操作的數據處理邏輯抽象,ChainedReference
爲所有中間操作的數據處理邏輯抽象
2. Stream 實現原理
2.1 Spliterator 可分割迭代器
Spliterator
可以看作一個splittable Iterator, 是 Java 8 中新增的一個迭代器。其缺省方法forEachRemaining(Consumer<? super E> action)
內部是一個循環,tryAdvance()
方法對下一個未處理的元素執行 action 並返回 true
, 如果沒有下一個元素返回 false
。另外它也提供了trySplit()
,實現數據源的拆解,爲多線程並行處理提供最小任務
/**
* Performs the given action for each remaining element, sequentially in
* the current thread, until all elements have been processed or the action
* throws an exception. If this Spliterator is {@link #ORDERED}, actions
* are performed in encounter order. Exceptions thrown by the action
* are relayed to the caller.
*
* @implSpec
* The default implementation repeatedly invokes {@link #tryAdvance} until
* it returns {@code false}. It should be overridden whenever possible.
*
* @param action The action
* @throws NullPointerException if the specified action is null
*/
default void forEachRemaining(Consumer<? super T> action) {
do { } while (tryAdvance(action));
}
2.2 Stream 串行處理流程
-
以
ArrayList#stream()
方法作爲流處理的入口,從代碼看其實是調用了StreamSupport#stream()
方法,從而生成了操作記錄鏈表的頭節點ReferencePipeline.Head
,並將其引用返回public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) { Objects.requireNonNull(spliterator); return new ReferencePipeline.Head<>(spliterator, StreamOpFlag.fromCharacteristics(spliterator), parallel); }
-
外部持有了
ReferencePipeline.Head
引用,再調用 filter() 方法其實是調用到了ReferencePipeline#filter()
,此時會新建一個無狀態的中間操作,其重寫的opWrapSink()
規定了該操作的下游操作的Sink
是如何組織數據處理邏輯的。完成這些後將新建的中間操作引用返回,之後再調用 map() 方法流程與此類似@Override public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { Objects.requireNonNull(predicate); return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { @Override Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) { return new Sink.ChainedReference<P_OUT, P_OUT>(sink) { @Override public void begin(long size) { downstream.begin(-1); } @Override public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); } }; } }; }
-
當終結方法 forEach() 被調用時,首先調用
ForEachOps#makeRef()
新建最終操作ForEachOp
對象,之後調用了AbstractPipeline#evaluate()
開始執行操作中定義的數據處理邏輯@Override public void forEach(Consumer<? super P_OUT> action) { evaluate(ForEachOps.makeRef(action, false)); }
-
AbstractPipeline#evaluate()
需要判斷當前流是否是並行流,此處以串行流分析,則調用了terminalOp.evaluateSequential()
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) { assert getOutputShape() == terminalOp.inputShape(); if (linkedOrConsumed) throw new IllegalStateException(MSG_STREAM_LINKED); linkedOrConsumed = true; return isParallel() ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags())) : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags())); }
-
terminalOp.evaluateSequential()
方法調用到重寫方法ForEachOp#evaluateSequential()
,可以看到其內部邏輯很清晰,其實就是調用到了AbstractPipeline#wrapAndCopyInto()
@Override public <S> Void evaluateSequential(PipelineHelper<T> helper, Spliterator<S> spliterator) { return helper.wrapAndCopyInto(this, spliterator).get(); }
-
AbstractPipeline#wrapAndCopyInto()
中包含了整個流中最重要的邏輯,其主要是分爲了兩個步驟:wrapSink()
從操作鏈表的尾部開始,調用操作對象自身重寫的 opWrapSink()方法將每一個操作對象中的數據處理邏輯封裝成 Sink.ChainedReference,並將傳入的 Sink 作爲新建 Sink 的 downStream,從而形成單向調用鏈copyInto()
從調用鏈頭部開始執行中間操作數據處理邏輯封裝成的Sink
對象的方法,完成對數據源的處理
@Override final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) { copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator); return sink; }
-
AbstractPipeline#wrapSink()
是數據處理邏輯從中間操作中抽取出來封裝成Sink
對象的關鍵步驟,其主要邏輯是回調中間操作對象的opWrapSink()
方法,將其中包含的數據處理邏輯封裝爲新的Sink
,並將當前的Sink
作爲新建Sink
的 downStream,促使單向調用鏈成型final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) { Objects.requireNonNull(sink); for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) { sink = p.opWrapSink(p.previousStage.combinedFlags, sink); } return (Sink<P_IN>) sink; }
-
AbstractPipeline#copyInto()
方法主要負責調用Sink
鏈,spliterator.forEachRemaining()
藉助迭代器對每一個未被遍歷的元素應用對數據的處理邏輯final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) { Objects.requireNonNull(wrappedSink); if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) { wrappedSink.begin(spliterator.getExactSizeIfKnown()); spliterator.forEachRemaining(wrappedSink); wrappedSink.end(); } else { copyIntoWithCancel(wrappedSink, spliterator); } }
-
ArrayListSpliterator#forEachRemaining()
中,action.accept()
在當前Sink#accept()
方法被調用後,會調用下一個Sink
的相關方法完成對數據的流處理public void forEachRemaining(Consumer<? super E> action) { int i, hi, mc; // hoist accesses and checks from loop ArrayList<E> lst; Object[] a; if (action == null) throw new NullPointerException(); if ((lst = list) != null && (a = lst.elementData) != null) { if ((hi = fence) < 0) { mc = lst.modCount; hi = lst.size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings("unchecked") E e = (E) a[i]; action.accept(e); } if (lst.modCount == mc) return; } } throw new ConcurrentModificationException(); }
2.3 Stream 並行處理流程
-
流的並行處理與串行處理差別不是很大,主要的差異在於當終結操作被觸發時,其調用的是
terminalOp.evaluateParallel()
,最終調用到其重寫方法ForEachOp#evaluateParallel()
,然後生成了ForEachTask
這個ForkJoinTask
的子類,並調用其invoke()
提交到線程池執行需注意該步驟中的
helper.wrapSink(this)
會在生成線程池任務的時候生成 Sink 調用鏈@Override public <S> Void evaluateParallel(PipelineHelper<T> helper, Spliterator<S> spliterator) { if (ordered) new ForEachOrderedTask<>(helper, spliterator, this).invoke(); else new ForEachTask<>(helper, spliterator, helper.wrapSink(this)).invoke(); return null; }
-
ForkJoinPool
相關流程請參考 Java 線程池源碼詳解(2)-ForkJoinPool 源碼解析,可知最後會調用到ForEachTask#compute()
方法。這個方法內部會開啓while
循環對任務進行估算,然後fork()
分解大任務,直到任務足夠小才執行task.helper.copyInto(taskSink, rightSplit)
,之後的數據處理流程與串行流完全一致// Similar to AbstractTask but doesn't need to track child tasks public void compute() { Spliterator<S> rightSplit = spliterator, leftSplit; long sizeEstimate = rightSplit.estimateSize(), sizeThreshold; if ((sizeThreshold = targetSize) == 0L) targetSize = sizeThreshold = AbstractTask.suggestTargetSize(sizeEstimate); boolean isShortCircuit = StreamOpFlag.SHORT_CIRCUIT.isKnown(helper.getStreamAndOpFlags()); boolean forkRight = false; Sink<S> taskSink = sink; ForEachTask<S, T> task = this; while (!isShortCircuit || !taskSink.cancellationRequested()) { if (sizeEstimate <= sizeThreshold || (leftSplit = rightSplit.trySplit()) == null) { task.helper.copyInto(taskSink, rightSplit); break; } ForEachTask<S, T> leftTask = new ForEachTask<>(task, leftSplit); task.addToPendingCount(1); ForEachTask<S, T> taskToFork; if (forkRight) { forkRight = false; rightSplit = leftSplit; taskToFork = task; task = leftTask; } else { forkRight = true; taskToFork = leftTask; } taskToFork.fork(); sizeEstimate = rightSplit.estimateSize(); } task.spliterator = null; task.propagateCompletion(); }