
저자: 라울-게이브리얼 우르마 , 마리오 푸스코 , 앨런 마이크로프트
도서명: 모던 자바 인 액션
출판사: 한빛미디어
컬렉션에 parallelStream을 호출하면 병렬 스트림이 생성된다.
병렬 스트림이란 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림이다.
병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당할 수 있다.
@Benchmark // 벤치마크 대상 메서드
public long numberSum() { // for문 사용
long result = 0;
for (long i = 1L; i <= N; i++) {
result += i;
}
return result;
}
@Benchmark
public long streamNumberSum() { // Stream.iterate 사용
return Stream.iterate(1L, i -> i + 1)
.limit(N)
.reduce(0L, Long::sum);
}
@Benchmark
public long streamParallelNumberSum() { // Stream.iterate 사용, 병렬처리
return Stream.iterate(1L, i -> i + 1)
.limit(N)
.parallel()
.reduce(0L, Long::sum);
}
@Benchmark
public long longStreamNumberSum() { // LongStream.rangeClosed 사용
return LongStream.rangeClosed(1L, N)
.reduce(0L, Long::sum);
}
@Benchmark
public long longStreamParallelNumberSum() { // LongStream.rangeClosed 사용, 병렬 처리
return LongStream.rangeClosed(1L, N)
.parallel()
.reduce(0L, Long::sum);
}
Benchmark Mode Cnt Score Error Units
ParallelStreamBenchmark.longStreamNumberSum avgt 10 4.744 ± 0.414 ms/op
ParallelStreamBenchmark.longStreamParallelNumberSum avgt 10 2.365 ± 2.894 ms/op
ParallelStreamBenchmark.numberSum avgt 10 2.521 ± 0.070 ms/op
ParallelStreamBenchmark.streamNumberSum avgt 10 73.067 ± 3.583 ms/op
ParallelStreamBenchmark.streamParallelNumberSum avgt 10 90.832 ± 4.587 ms/op
참고
https://log-laboratory.tistory.com/203
https://mong9data.tistory.com/131
공유된 상태를 바꾸는 알고리즘을 사용할 때 병렬 스트림을 사용하면 문제가 발생한다.
public long sideEffectParalleSum(long n) {
Accumulator accumulator = new Accumulator();
LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
return accumulator.total;
}
public class Accumulator {
public long total = 0;
public void add(long value) { total += value; }
}
이펙티브 자바에서는 병렬 스트림을 아예 사용하는걸 지양하기를 권장한다.
그 이유는 위에 소개한바와 같다. 하지만 그래도 병렬 스트림을 이용해 성능 개선을 노려볼 생각이라면 다음의 내용이 약간의 힌트는 될 수 있다.
| 자료구조 | 분해 성능 |
|---|---|
| ArrayList | 매우 좋음 |
| LinkedList | 나쁨 |
| IntStream.range | 매우 좋음 |
| Stream.iterate | 나쁨 |
| HashSet | 좋음 |
| TreeSet | 좋음 |
병렬 스트림이 수행되는 내부 인프라구조는 자바7에서 추가된 포크/조인 프레임워크로 병렬 스트림이 처리된다.
protected abstract R compute();

fork()가 호출되어 작업 큐에 추가된 작업 역시, compute()에 의해 더 이상 나눌 수 없을 때까지 반복해서 나뉘고, 자신의 작업 큐가 비어있는 쓰레드는 다른 쓰레드의 작업 큐에서 작업을 가져와서 수행한다.
이것을 작업 훔쳐오기라고 하며, 이 과정은 모두 쓰레드풀에 의해 자동적으로 이루어진다.

그렇다면 스트림은 어떻게 분할 로직을 개발하지 않고도 자동으로 스트림을 분할할까??
스트림을 자동으로 분할해주는 기능이 이미 존재하기 때문인데, 이 기능은 스트림을 분할하는 기법인 Spliterator을 이용하는 것이다.
public interface Spliterator<T> {
boolean tryAdvanace(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
