Java Streams API는 Java 8에 도입된 기능으로, 컬렉션(예: List, Set, Map 등)의 데이터 처리 작업을 선언적 방식으로 수행할 수 있게 해줍니다. 이는 함수형 프로그래밍의 개념을 도입하여 코드의 가독성과 간결성을 높이며, 병렬 처리의 이점을 쉽게 활용할 수 있게 합니다. 다음은 Java Streams API에 대한 전반적인 설명입니다.
스트림의 기본 개념
스트림(Stream)은 데이터의 연속된 요소들을 지원하며, 다양한 중간(intermediate) 및 최종(terminal) 연산을 적용할 수 있습니다. 스트림은 데이터 소스로부터 생성되며, 요소들을 필터링, 변환, 집계하는 등의 작업을 수행할 수 있습니다.
스트림의 구성
- 데이터 소스: 스트림은 컬렉션, 배열, I/O 채널 등으로부터 생성됩니다.
- 중간 연산: 스트림을 변환하거나 필터링하는 연산으로, 항상 또 다른 스트림을 반환합니다. 예: filter, map, sorted, distinct.
- 최종 연산: 스트림을 소모하여 결과를 생성하는 연산으로, 더 이상 스트림을 사용할 수 없습니다. 예: forEach, collect, reduce, count.
스트림 생성
스트림을 생성하는 방법은 여러 가지가 있습니다
List<String> myList = Arrays.asList("a", "b", "c");
// 컬렉션으로부터 스트림 생성
Stream<String> stream = myList.stream();
// 배열로부터 스트림 생성
String[] myArray = {"a", "b", "c"};
Stream<String> arrayStream = Arrays.stream(myArray);
// 개별 요소로부터 스트림 생성
Stream<String> elementStream = Stream.of("a", "b", "c");
중간 연산
중간 연산은 스트림을 변환하며, 또 다른 스트림을 반환합니다. 일반적인 중간 연산은 다음과 같습니다:
- filter: 조건에 맞는 요소만 포함하는 스트림을 반환합니다.
- map: 각 요소를 변환하여 새로운 스트림을 반환합니다.
- sorted: 스트림의 요소를 정렬합니다.
- distinct: 중복된 요소를 제거합니다.
예제
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0);
최종 연산
최종 연산은 스트림을 소모하여 결과를 생성합니다. 일반적인 최종 연산은 다음과 같습니다:
- forEach: 각 요소에 대해 작업을 수행합니다.
- collect: 스트림의 요소들을 컬렉션으로 수집합니다.
- reduce: 스트림의 요소들을 하나의 값으로 누적합니다.
- count: 스트림의 요소 개수를 반환합니다.
예제:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 짝수들의 합을 계산
int sumOfEven = numbers.stream()
.filter(n -> n % 2 == 0)
.reduce(0, Integer::sum);
병렬 스트림
스트림을 병렬로 처리하면 성능을 향상시킬 수 있습니다. 병렬 스트림은 내부적으로 여러 스레드를 사용하여 작업을 분할합니다:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 병렬 스트림 생성
int sumOfEven = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.reduce(0, Integer::sum);
Java Streams API는 컬렉션과 배열의 데이터를 처리하는 데 있어 강력하고 유연한 방법을 제공합니다. 선언적 프로그래밍 스타일을 채택하여 코드의 가독성을 높이고, 병렬 처리를 통해 성능을 개선할 수 있습니다. Streams API를 사용하면 데이터 변환, 필터링, 집계 등의 작업을 간단하고 효율적으로 수행할 수 있습니다.
Thread Safe 관점
Java Streams API 자체는 본질적으로 thread-safe하지 않습니다. 그러나 Streams API를 통해 병렬 처리를 할 때, 내부적으로는 thread-safe한 방식으로 작업을 수행합니다. 여기에서 중요한 점은 병렬 스트림을 사용할 때 데이터 소스와 스트림 연산이 어떻게 동작하는지를 이해하는 것입니다.
병렬 스트림과 thread safety
병렬 스트림(parallel stream)은 내부적으로 ForkJoinPool을 사용하여 병렬로 작업을 처리합니다. 이때, 스트림의 요소를 여러 스레드에 분배하여 작업을 수행합니다. 하지만 데이터 소스와 스트림 연산이 thread-safe하지 않다면, 병렬 스트림을 사용할 때 문제가 발생할 수 있습니다.
데이터 소스의 thread safety
병렬 스트림을 사용할 때 데이터 소스가 thread-safe해야 합니다. 예를 들어, ArrayList와 같은 비동기화된 컬렉션은 병렬 스트림에서 안전하게 사용할 수 없습니다. 대신 Collections.synchronizedList나 CopyOnWriteArrayList와 같은 동기화된 컬렉션을 사용하는 것이 좋습니다.
중간 및 최종 연산의 thread safety
스트림 연산 중간에 사용하는 함수들은 모두 thread-safe해야 합니다. 예를 들어, 상태를 가지지 않는 순수 함수(pure function)를 사용하는 것이 좋습니다. 상태를 가지는 연산은 병렬 처리 중에 데이터 경합(race condition)을 일으킬 수 있습니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 비상태 중간 연산과 thread-safe 최종 연산
int sumOfEven = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.reduce(0, Integer::sum);
위의 예제에서는 중간 연산(filter)과 최종 연산(reduce) 모두 비상태(stateless) 연산이므로 병렬 스트림에서도 안전하게 작동합니다.
상태를 가지는 연산의 문제
상태를 가지는 연산은 병렬 스트림에서 문제가 될 수 있습니다. 예를 들어, 외부 변수를 수정하는 경우 race condition이 발생할 수 있습니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int[] sum = {0};
// 상태를 가지는 중간 연산 (비추천)
numbers.parallelStream().forEach(n -> sum[0] += n);
위의 예제는 여러 스레드가 동시에 sum[0]을 수정하기 때문에 thread-safe하지 않습니다. 이 문제를 해결하려면 reduce와 같은 thread-safe한 최종 연산을 사용해야 합니다.
안전한 병렬 스트림 사용 예
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 안전한 병렬 스트림 사용
int sumOfEven = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.reduce(0, Integer::sum);
이 예제에서는 모든 중간 및 최종 연산이 상태를 가지지 않으므로 병렬 스트림에서도 안전하게 작동합니다.
결론
Java Streams API 자체는 thread-safe하지 않지만, 병렬 스트림(parallel stream)을 사용할 때 내부적으로 thread-safe한 방식으로 작업을 처리합니다. 중요한 점은 데이터 소스와 스트림 연산이 thread-safe하도록 설계하는 것입니다. 비상태(stateless) 연산을 사용하고, 동기화된 컬렉션을 사용하는 것이 좋은 방법입니다. 이렇게 하면 병렬 스트림의 성능 향상을 안전하게 활용할 수 있습니다.
'프로그래밍 > JAVA' 카테고리의 다른 글
Java stream 의 Optional.orElseThrow (0) | 2024.07.28 |
---|---|
Multi Datasource 를 이용한 database routing 전략 (0) | 2024.07.06 |
Java Streams API - 3 (0) | 2024.05.26 |
Java Streams API - 2 (0) | 2024.05.26 |