자바 parallelStream()사용시 예상되는 문제점과 그 이유에 대해 설명드리겠습니다.

 

 

자바 parallelStream() 메서드 사용시 예상되는 문제점

 

parallelStream() 메서드는 자바 8에서 추가된 스트림 API 기능 중 하나입니다.  parallelStream()은 자바에서 멀티 코어 프로세서를 통해 데이터를 병렬로 처리할 수 있게 해주는 메서드입니다. parallelStream() 메서드를 사용하면 일반적으로 코드 작성이 간결해지고, 데이터 처리 속도가 빨라지는 장점이 있습니다. 

 

 

 

 제약사항 

parallelStream()을 사용하면 스트림을 병렬처리하여 처리속도를 높여줍니다. 하지만 아래와 같은 제약사항을 고려하지 않고 사용하게 되면 여러가지 문제점들이 발생할 수 있습니다. 제약사항은 다음과 같습니다.

 

1. 처리 순서가 보장되지 않음

2. 메모리 사용량이 증가함

3. 성능 개선 효과가 불확실함

 

다시말하면 parallelStream()을 도입할 때 위 제약사항에 의해 처리 결과가 영향을 받지 않는 경우에 사용해야한다는 것을 의미합니다. 제약사항을 지키지 못하는 경우에도 여러가지 적절한 해결방법을 사용하여 parallelStream()을 사용할 수 있습니다.

 

 

 예상 문제점 

여러가지 문제점이 있을 수 있지만 대표적으로는 아래 세가지를 들 수 있습니다. 

 

1. 동기화 문제

스레드 안정성이 깨지는 경우로, 동시에 여러 스레드에서 같은 데이터에 접근하면서 값을 수정하는 경우에 생기는 문제입니다. 대표적으로 경쟁 상태 (Race Condition) 문제가 있습니다.

 

 

아래 코드는 parallelStream() 메서드를 사용해서 리스트 자료형인 numbers에 저장된 정수를 제거하는 예시입니다. 

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            numbers.add(i);
        }
        numbers.parallelStream().forEach(num -> {
            System.out.println(Thread.currentThread().getName() + " : " + num);
            numbers.remove(num);
        });
    }
}

 

 여기서 numbers 리스트는 스레드간에 공유되는 데이터입니다. parallelStream()은 이 리스트를 chunk로 분할해서 병렬적으로 처리하게 되는데, 문제는 여러 스레드가 동시에 같은 chunk에 접근할 수 있다는 점 입니다. 경우에 경쟁상태에 의해 동기화 문제가 발생하여 스레드 안정성이 깨질 수 있고 구체적으로는 다음 두가지 문제를 예상할 수 있습니다.

 

1) ConcurrentModificationException 발생

ArrayList 자료형은 내부적으로 배열을 사용하기 때문에, 내부적으로 요소를 제거하면 나머지 요소를 한칸씩 이동시켜줍니다. 동시에 다른 스레드가 같은 요소를 제거하려 Iterator로 접근하며 내부적으로 next() 메서드를 호출합니다.

 

 이때 Iterator가 내부적으로 가지고 있는 expectedmodCount와 ArrayList의 modCount를 비교합니다. 이전에 요소에 접근해 제거한 스레드 때문에 값이 다른 것을 확인하고 동기화 문제가 발생한 것으로 간주하여 next() 메서드에서  ConcurrentModificationException을 발생시킵니다.

 

2) 요소가 중복으로 출력됨

 parallelStream()으로 처리하면서 동기화 문제가 발생한 경우 어느 한 스레드가 제거하면서 출력한 요소를 동시에 다른 요소에서 접근해서 또 제거를 시도하면서 요소를 출력하게 됩니다. 이 경우에 parallelStream()으로 정상적으로 처리됐다면 중복된 요소가 출력되는 경우는 없어야하지만, 이 경우에는 같은 요소가 출력될 수 있습니다. 

 그리고 remove()메서드는 제거된 요소를 다시 제거해도 예외가 발생하지 않고 무시되기 때문에 출력된 결과를 보고나서야 문제를 확인할 수 있습니다.

 

 

 위 두가지 문제점은 결국 공유하고 있는 원소에 여러 스레드가 동시에 접근할 수 있는 것이 원인이기 때문에 동기화 기법을 통해 문제를 해결할 수 있습니다. 하지만 동기화 기법으로 문제를 해결하게 되면 parallelStream() 메서드의 병렬처리를 통한 이점이 반감될 수 있습니다.

 그래서 parallelStream()을 적용하는 위치에서 어떤 자료형을 쓰고 있고 어떤 로직인지를 잘 살펴보고 이해할 필요가 있습니다. 만약 코드상에서 위 문제점이 발생해도 상관없다면 ConcurrentModificationException은 예외처리하고 요소가 중복출력되는 것은 무시하면 되기 때문입니다.

 

 정리하면 parallelStream()을 사용할지 말지 여부를 결정할 때 중요한 점은 parallelStream()을 사용했을 때 어떤 문제점이 발생하는지 미리 예상할 수 있는 것, 예상되는 제약사항을 인지 하는 것, 그 제약사항이 코드상에서 독립적이라서 무시해도되는지 아니면 동기화를 해서 처리해야하는지 여부를 상황에 따라 잘 결정하는 것입니다.

 

 

 

2. 처리 순서 의존성(Order Dependency)

parallelStream()은 순서를 보장하지 않는다는 제약사항이 있습니다. 구현하려는 코드에 따라서 중간 처리 과정이 이전 요소의 처리 결과에 의존하는 경우가 있을 수도 있고, 순서를 보장하지 않으면 일관된 결과를 보장할 수 없는 경우도 있을 수 있습니다. 이런 경우에는 마찬가지로 스트림 인터페이스에서 제공하는 순서 보장 메서드를 사용하여 문제를 해결할 수 있습니다.

 

하지만 아래 간단한 예시처럼 원하는 처리결과가 순서에 의존하지 않고, 요소가 독립적인 경우에는 동기화를 사용하지 않아도 됩니다.

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        list.parallelStream()
                .map(num -> num * 2)
                .forEach(System.out::println);
    }
}

위 코드에서 2씩 곱하는 연산은 순서에 상관없이 일관된 결과값을 보장합니다. 이와 같이 처리 순서에 의존성이 없고 요소가 독립적인 작업을 수행할 때는 동기화가 필요없으며, parallStream()을 사용하기 적합한 경우라고 볼 수 있습니다.

 

 

3. 병렬 처리가 더 느린 경우

 병렬로 처리하는 것이 항상 빠른 속도임을 보장하지는 않습니다. 데이터를 병렬로 처리하는 과정에서 Stream보다 처리속도가 느려질 수 있습니다. 이 때는 스트림 요소의 개수나 복잡도를 고려하여 성능이 개선될지를 미리 검토후 적용하는 것이 중요합니다.

 

4. 메모리 사용량 증가

parallelStream()을 사용하게 되면 스트림의 각 요소를 여러 스레드에서 병렬로 처리하기 때문에, 처리 중에 생성되는 객체의 수가 증가하고 이로 인해 메모리 사용량이 증가할 수 있습니다.

 

 

 정리 

  parallelStream() 사용시 제약사항과 발생할 수 있는 문제점에 대해서 알아보았습니다. 본문에서 언급했듯이 각 상황에 맞게 그대로 stream()을 사용하는게 나을 수도, 동기화 문제를 해결하며 사용해야할 수 도 있습니다. 알맞은 방법으로 parallStream()을 사용하면 어떤 이점이 있고 단점이 있는지를 잘 고려해서 적용하는 것이 중요한 것 같습니다. 적재적소에 잘 사용한다면 어플리케이션을 더 가용성있고 더 많은 데이터를 빠르게 처리할 수 있을 것으로 생각됩니다.

 

 

 다음 포스팅은 parallelStream()을 사용했을 때 발생할 수 있는 문제점들에 대한 해결방안에 대해서 간단한 예시코드를 사용한 설명을 작성해보았습니다. 아래 링크를 통해 이동할 수 있습니다.

 

[Java] parallelStream() 메서드 사용시 예상되는 문제점 및 해결방안 [2]

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기