스트림으로 데이터 컬렉션 관련 동작을 얼마나 쉽게 병렬로 실행할 수 있는 지 설명
- 자바7 이전
1. 서브파트로 분할
2. 분할된 서브파트를 스레드로 할당
3. 스레드 경쟁상태가 되지 않도록 동기화
4. 마지막으로 부분 결과를 합쳐야함
- 자바 7
포크, 조인 프레임워크 기능 제공
- 자바 8
병렬 스트림
병렬 스트림
컬렉션에 parallelStream을 호출하면 병렬 스트림(parallel stream)이 생성
- EX) 숫자 n을 인수로 받아서 1부터 n까지의 모든 숫자의합계를 반환하는 메서드를 구현
- 순차 스트림에 parallel() 메서드를 호출하면 기존의 함수형 리듀싱 연산(숫자 합계 계산)이 병렬로 처리
- 반면에 sequential() 메서드를 호출하면 순차 스트림으로 변경
- sequential 메서드가 호출된 중간연산은 순차로 parallel 메서드가 호출된 중간연산은 병렬로, 마지막 호출은 parallel 메서드 임으로 파이프라인은 전체적으로 병렬로 실행
- 스트림의 parallel 메서드에서 병렬 수행은 내부적으로 ForkJoinPool을 사용
→ ForkJoinPool은 프로세서 수에 상응하는 스레드를 가짐
→ 프로세서 수 = Runtime.getRuntime().availableProcessor()
- 코어 간 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로 다른 코어에서 수행하는 것이 바람직
- 병렬처리와 스트림소스
- very good
ArrayList
IntStream.range
- good
HashSet
TreeSet
- bad
LinkedList
Stream.iterate
- 병렬 스트림 효과적으로 사용하기
1. 직접 측정해보고 사용
2. 박싱에 주의
3. limit나 findFirst처럼 요소의 순서에 의존하는 병렬 스트림을 수행하면 성능이 저하
4. 스트림에서 수행하는 전체 파이프라인 연산 비용을 고려
5. 소량에 데이터는 이득을 얻지 못함
6. 자료구조 확인(ArrayList는 배열임으로 분할에 유리하나 LinkedList는 배열을 분할하기 위해 탐색이 필요)
7. 최종 연산 병합 과정 비용 확인(비용이 비싸다면 병렬처리 성능 이익이 사라질 수 있음)
포크/조인 프레임워크
병렬화 할 수 있는 작업을 재귀적으로 작은 작업으로 분할한 다음 서브태스크 각각의 결과를 합쳐서 전체 결과를 도출하도록 설계
서브태스크를 스레드 풀(ForkJoinPool)의 작업자 스레드에 분산 할당하는 ExecutorService 인터페이스를 구현
- 스레드 풀을 사용하려면 RecursiveTask<R>의 서브클래스 생성 필요
→ R = RecursiveAction(결과가 없더라도 다른 비지역 구조 변경 가능)
- 추상 메서드 compute 구현
- ForkJoinSumCalculator 실행
- ForkJoinSumCalculator를 ForkJoinPool로 전달하면 풀의 스레드가 compute 메서드를 실행하며 작업을 수행
- compute는 위에 선언한 바와 같이 태스크 크기가 작아질 때까지 분할해서 두 개의 새로운 ForkJoinSumCalculator에 할당
- 다단계처럼 아래로 가지치기, 더 이상 뽑아먹을게 없어지면 도주(실행)
- 포크/조인 프레임워크 제대로 쓰기
- join 메서드를 태스크에 호출하면 태스크가 생산하는 결과가 준비될 때까지 호출자를 블록, 두 서브 태스크가 모두 시작된 다음 join을 호출 → 먼저 시작하면 교착상태
- RecursiveTask 내에서 invoke 메서드 사용 금지 → invoke는 순차 코드에서 병렬 계산을 시작할 때만
- 서브태스크에서 ForkJoinPool 일정 조절 → 한 쪽은 fork, 한 쪽은 compute로 사용하면 같은 스레드 재사용 가능
- 실행 결과 측정하여 사용
- 작업 훔치기
→ ForkJoinPool의 모든 스레드를 거의 공정하게 분할
→ 이중 연결 리스트를 참조하면서 작업이 끝날 때 마다 큐의 헤드에서 다른 태스크를 가져와 작업을 처리: 그래서 작업 훔치기
Spliterator 인터페이스
- 자바 8에 등장
- 분할할 수 있는 반복자
- Iterator와 같이 소스의 요소 탐색 기능을 제공, 병렬에 특화
- 자바 8 컬렉션에 포함된 모든 자료구조에서 디폴트 Spliterator 제공
- tryAdvance: Spliterator의 요소를 하나씩 순차적으로 소비하면서 탐색해야할 요소가 남아 있으면 참을 반환(=Iterator)
- trySplit: Spliterator의 일부 요소를 분할하여 두 번째 Spliterator를 생성
- estimateSize: 탐색해야할 요소 수에 대한 정보 제공
- 이진트리 전위순회하면서 동시에 오른쪽 가지 치는 느낌?
- 특성
1. ORDERED: 요소에 정해진 순서
2. DISTINCT: 두 요소는 항상 같지 않음
3. SORTED: 탐색된 요소는 미리 정의된 정렬 순서에 따름
4. SIZED: 정확한 크기
5. NON-NULL: 탐색하는 모든 요소는 non null
6. IMMUTABLE: 불변, 탐색하는 동안 추가, 삭제, 수정 불가능
7. CONCURRENT: 동기화없으 여러 스레드에서 동시에 수정 가능
8. SUBSIZED: 분할되는 모든 Spliterator는 SIZED 특성