Java

[Java] AtomicInteger - 자바 스트림에서 외부 변수를 변경할 수 있을까?

태오님 2024. 3. 15.

문제 상황

int a = 0;

Arrays.stream(arr).map(value -> {
    value += a;
    a++; // 예외 발생
    }
)

위의 코드처럼 외부 변수인 a를 스트림 안에서 변경을 하면 오류가 발생한다.


이유

Java Stream은 함수형 프로그래밍의 원칙을 따르기 때문에 부작용을 최소화하고 불변성을 강조한다.
Stream은 데이터를 처리하는데 있어서 부작용을 최소화하고자 요소에 대한 연산을 수행할 때 각 단계에서 새로운 스트림을 생성하거나 기존 스트림을 변경하지 않고, 요소를 변환하는 연산들을 수행한다.
만약 외부 변수를 변경하면서 스트림을 사용하게 되면, 코드의 예측이 어려워지고 병렬 처리 및 lazy evaluation과 같은 이점이 상실될 수 있다.

  • 결국 가변성이 있는 외부 요소가 Stream의 연산의 결과에 부작용을 초래할 수 있기 때문에 Stream 내부에서 외부 변수의 변경을 막아놓은 것이다.
  • 하지만 우린 이것을 극복할 수 있다.

해결 방법

  1. 기존 로직을 변경하여 외부 변수를 사용하지 않기
  2. 로직을 유지하고 외부 변수를 기존처럼 사용하기

2번 방법은 어떻게 실현시킬 수 있을까?
Stream의 원칙에 맞게 외부 변수를 변경해야 한다.
외부 변수를 원시타입이 아닌 객체타입으로 변경하면 기존 로직을 유지할 수 있다!
(배열, 커스텀 컨테이너 클래스 등)

이번에 새롭게 알게된 AtomicInteger를 사용해보자.


AtomicInteger 란?

'AtomicInteger은 자바에서 다중 스레드 환경에서 안전하게 원자적(atomic) 연산을 수행할 수 있도록 설계된 클래스이다. 여러 스레드에서 공유되는 변수를 동기화 없이 안전하게 변경할 수 있도록 하는데 사용된다.
여러 스레드가 동시에 해당 변수를 읽고 쓰는 경우, 일반적으로 동기화 없이는 예측할 수 없는 결과가 발생할 수 있다. 하지만 AtomicInteger은 이런 상황에서 원자적인 연산을 제공하여, 읽기와 쓰기 연산이 각각 한 번의 연산으로 수행되도록 보장한다.


예시 코드

List<Integer> integerList = List.of(1, 2, 3, 4, 5);

// AtomicInteger를 외부 변수로 사용
AtomicInteger sum = new AtomicInteger(0);

// 리스트의 각 요소를 스트림으로 처리하면서 AtomicInteger를 업데이트
integerList.stream().forEach(element -> sum.addAndGet(element));

// 결과 출력
System.out.println("Sum: " + sum.get());

후기

Stream 내부에서 외부 변수를 변경하고자 객체를 추가로 생성하는 것은 비용이 많이들 수 있다고 생각한다.
개인적으로 이런 상황이 발생할 시 최대한 추가 비용이 발생하지 않게 Stream의 내부 로직을 변경할 것 같다.
결론은 설계를 잘하자..

댓글