본문 바로가기
language/java

병렬 Stream(parallelStream) 주의점 완벽 이해하기

by 죄니안죄니 2026. 6. 1.
반응형

병렬 Stream(parallelStream) 주의점 완벽 이해하기

많은 개발자가 처음 병렬 Stream을 보면 감탄한다.

 
list.stream()
 

 
list.parallelStream()
 

으로만 바꾸면

멀티코어 사용
↓
성능 향상
 

이 될 것 같기 때문이다.

하지만 실무에서는 오히려:

parallelStream() 때문에
성능이 더 느려지는 경우
 

가 정말 많다.


1. Stream vs parallelStream

일반 Stream

 
list.stream()
 

병렬 Stream

 
list.parallelStream()
 

차이

stream()
 ↓
순차 실행

parallelStream()
 ↓
병렬 실행
 

2. 예제

 
list.parallelStream()
    .forEach(System.out::println);
 

내부적으로

ForkJoinPool
 

사용.


3. 실제 동작

100만 건 데이터.

1 ~ 1,000,000
 

parallelStream

1~250000

250001~500000

500001~750000

750001~1000000
 

자동 분할.


4. 그럼 무조건 빠른가?

아니다.


5. 병렬 처리 비용 존재

ForkJoinPool은:

작업 분할

Thread 할당

Context Switching

결과 병합
 

비용 발생.


6. 작은 작업

예:

 
list.parallelStream()
    .map(x -> x + 1)
 

데이터

100개
 

결과

병렬 처리 비용

>

실제 작업 비용
 

오히려 느려짐.


7. 대표적인 실수

 
users.parallelStream()
     .map(User::getName)
     .toList();
 

데이터

100건
 

거의 무조건 손해.


8. 언제 효과가 좋을까?

작업량이 클 때.


예:

수백만 건

복잡한 계산

CPU 많이 사용
 


9. CPU Bound 작업

대표.

암호화

압축

영상 처리

이미지 처리

수학 계산
 

병렬 Stream 효과 좋음.


10. 예시

 
numbers.parallelStream()
       .map(this::heavyCalculation)
       .toList();
 

CPU 사용량 높음.

병렬 처리 효과 큼.


11. I/O Bound 작업

대표.

DB 조회

REST API 호출

파일 읽기
 

12. 예시

 
users.parallelStream()
     .map(userService::findUser)
     .toList();
 

매우 위험.


13. 왜?

ForkJoinPool Thread가

DB 응답 대기
 

상태가 됨.


CPU는 놀고 있음.


14. ForkJoinPool 설계 목적

원래

CPU Bound 작업
 

용.


I/O 작업용 아님.


15. 실무에서 많이 하는 실수

 
ids.parallelStream()
   .map(id -> restTemplate.getForObject(...))
 

생각.

병렬이니까 빠르겠지?
 

실제.

Thread 고갈

Connection Pool 부족

응답 지연
 

16. 순서 문제

일반 Stream.

 
list.stream()
 

입력.

1 2 3 4 5
 

출력.

1 2 3 4 5
 

17. parallelStream

 
list.parallelStream()
 

출력.

3 1 5 2 4
 

가능.


18. 왜?

여러 Thread가 동시에 처리.


순서 보장 안 함.


19. 예제

 
list.parallelStream()
    .forEach(System.out::println);
 

결과.

순서 랜덤
 

20. 순서 보장하려면

 
forEachOrdered()
 

예:

 
list.parallelStream()
    .forEachOrdered(
        System.out::println
    );
 

21. 문제

순서 보장 비용 발생.


병렬 효과 감소.


22. 공유 객체 문제

매우 중요.


잘못된 코드.

 
List<Integer> result =
        new ArrayList<>();

list.parallelStream()
    .forEach(result::add);
 

23. 문제

ArrayList

Thread-Safe 아님
 

결과.

데이터 유실

Index 오류
 

가능.


24. 왜?

여러 Thread가 동시에.

 
result.add(...)
 

수행.


Race Condition 발생.


25. 해결

Collector 사용.


좋은 예.

 
List<Integer> result =

list.parallelStream()

    .map(...)

    .toList();
 

Stream이 내부적으로 안전하게 병합.


26. synchronized 사용?

가능.


 
Collections.synchronizedList(...)
 

하지만.

Lock 경쟁
 

발생.


성능 감소.


27. ThreadLocal 문제

실무 중요.


예:

 
ThreadLocal<UserContext>
 

사용.


parallelStream

ForkJoinPool Thread 사용.


예상 못한 값 공유 가능.


주의 필요.


28. 트랜잭션 문제

Spring 실무 핵심.


예:

 
@Transactional
public void process() {

    list.parallelStream()

}
 

29. 문제

Spring Transaction은

ThreadLocal
 

기반.


30. 병렬 Thread

새 Thread.


기존 트랜잭션 전달 안 됨.


31. 결과

트랜잭션 깨짐
 

가능.


32. SecurityContext 문제

Spring Security도.


ThreadLocal
 

사용.


parallelStream 내부.

로그인 정보 없음
 

가능.


33. ForkJoinPool 문제

매우 중요.


parallelStream은 기본적으로

 
ForkJoinPool.commonPool()
 

사용.


34. 공용 Pool

JVM 전체 공유.


parallelStream

CompletableFuture

라이브러리
 

같은 Pool 사용 가능.


35. 결과

Pool 고갈
 

가능.


36. 장애 사례

실제 많이 발생.


 
parallelStream()
 

남발.


ForkJoinPool Thread 전부 사용
 

다른 비동기 작업 정체.


37. 병렬 Stream이 적합한 경우

대표.

CPU 계산
 

대용량 데이터
 

공유 상태 없음
 

순서 중요하지 않음
 

38. 병렬 Stream이 부적합한 경우

대표.

DB 호출
 

REST API 호출
 

파일 I/O
 

트랜잭션 처리
 

ThreadLocal 사용
 

39. 실무 추천

DB 호출.


좋은 방법.

 
CompletableFuture
 

전용

 
ThreadPoolExecutor
 

사용.


40. 예시

 
CompletableFuture
    .supplyAsync(
        task,
        customExecutor
    );
 

Pool 직접 제어 가능.


41. 면접 단골 질문

Q. parallelStream 내부 구현은?

ForkJoinPool
 

Q. 무조건 빠른가?

아니다
 

Q. 어떤 작업에 적합한가?

CPU Bound
 

Q. DB 조회에도 쓰면 좋은가?

아니다
 

Q. 순서 보장되는가?

기본은 아니다
 

Q. Spring @Transactional과 같이 쓰면?

주의 필요

ThreadLocal 기반이라
트랜잭션 전파 안 됨
 

42. 실무 관점 정리

좋은 예

 
numbers.parallelStream()
       .map(this::encrypt)
       .toList();
 

나쁜 예

 
ids.parallelStream()
   .map(userRepository::findById)
   .toList();
 

더 나쁜 예

 
list.parallelStream()
    .forEach(result::add);
 

43. 핵심 흐름 요약

parallelStream()
 ↓
ForkJoinPool
 ↓
작업 분할
 ↓
병렬 처리
 ↓
결과 병합
 

44. 가장 중요한 핵심 한 줄

parallelStream은 ForkJoinPool 기반의 CPU Bound 병렬 처리에는 효과적이지만, DB/API 호출 같은 I/O 작업이나 ThreadLocal·트랜잭션이 연관된 환경에서는 성능 저하와 예상치 못한 문제를 유발할 수 있다.
 

45. 동시성 & 멀티스레드 마지막 주제

다음 글은:

Virtual Thread (Java 21)

이다.

이 글을 배우면 지금까지 공부한:

Thread

↓

ThreadPool

↓

ForkJoinPool

↓

CompletableFuture

↓

Virtual Thread
 

까지 Java 동시성의 발전 흐름이 완성된다.

특히 최근 실무 면접에서는

Virtual Thread가 기존 ThreadPool을 대체할 수 있나요?

질문이 매우 자주 나온다.

반응형

댓글