스트림을 이용하면 순차 스트림을 병렬 스트림으로 자연스럽게 바꿀 수 있다.
컬렉션의 parallelStream
을 호출하면 병렬 스트림이 생성된다.
각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림
iterate(final T seed, final UnaryOperator<T> f)
parallel()
과 sequential()
메서드를 이용하여 스트림을 병렬로 작동할지 순차로 실행할지 제어할 수 있다.
병렬 스트림에서 사용하는 스레드 풀 설정 - java.lang.Runtime
클래스 알아보기
Process exec(String command)
: 명령(command)을 실행시키고, 실행시킨 프로세스의 래퍼런스를 반환한다.static Runtime getRuntime()
: Runtime 객체의 레퍼런스를 반환한다.void exit(int status)
: 상태 값(status) 반환하면서 JVM을 종료시킨다.long freeMemory()
: JVM이 사용 가능한 메모리 양(bytes)을 반환한다.long totalMemory()
: JVM이 사용하고 있는 전체 메모리를 반환한다.long maxMemory()
: JVM이 사용할 수 있는 최대 메모리 양을 반환한다.int availableProcessors()
: JVM이 사용할 수 있는 프로세서수를 반환한다.7.1에서 다룬 반복형, 순차 리듀싱, 병렬 리듀싱의 성능을 비교한다.
병렬 스트림에서 발생 할 수 있는 문제점
리듀싱 연산의 경우 리듀싱 과정 시작 시점에 전체 숫자 리스트가 준비되어야 한다.
병렬화는 스트림을 재귀적으로 분할해야 하고(청킹), 각 서브스트림을 서로 다른 스레드의 리듀싱 연산으로 할당하고,
이들 겨로가를 하나의 값으로 합쳐야한다.
멀티코어 간의 데이터 이동의 비용은 생각보다 크다. 코어 간에 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로
작업하는 것이 낫다.
7.1.3에서는 잘못된 예시를 다루고 있다. 해결방법을 7.1.4에서 제공한다
class ParallelStreamExample {
// side effect가 있는
public static void main(String[] args) {
final int[] sum = {0};
IntStream.range(0, 1000).forEach(i -> {
sum[0] += i;
});
System.out.println(sum[0]);
final int[] sum2 = {0};
IntStream.range(0, 1000)
.parallel()
.forEach(i -> {
sum2[0] += i;
});
System.out.println(sum2[0]);
}
}
// 결과값: 499500
// 486145
위의 병렬 스트림의 경우 레이스 컨디션이 발생
class ParallelStreamExample {
// side effect가 없는 방법
public static void main(String[] args) {
IntStream.range(0, 1000).sum();
IntSream.range(0, 1000).parallel().sum();
}
}
서브태스크를 스레드 풀의 Executor 스레드에 분산 할당하는 ExecutorService
인터페이스를 제공한다
클래식한 예시로 피보나치 수가 있다.
public class Fibonacci extends RecursiveTask<Integer> {
private final int n;
Fibonacci(int n) {
this.n = n;
}
protected Integer compute() {
if (n <= 1) {
return n;
}
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
f2.fork();
return f2.compute() + f1.join();
}
}
분할할 수 있는 반복자를 의미한다.
병렬 스트림의 동작원리를 알 수 있다.
책의 예시를 잘 따라가보자...