1 minute read

Java의 동시성 프로그래밍

자바는 스레드, 동기화, wait/notify, java.util.concurrent 라이브러리와 실행자(Executor) (Java 5), 포크-조인(fork-join) 패키지 (Java 7), parallel()메서드 (Java 8) 등을 통해 동시성 프로그램을 지원한다.

동시성 프로그래밍을 할 때는 안정성(safety)과 응답 가능(liveness) 상태를 유지해야 함에 주의하자.

Stream 병렬화

사용하지 말아야 할 경우

public class ParallelMersennePrimes {
    public static void main(String[] args) {
        primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
                .parallel() // 병렬화 
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(System.out::println);
    }

    static Stream<BigInteger> primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }
}

무작정 성능을 향상시키기 위해 parallel() 를 사용하면, 위와 같이 아무것도 출력하지 못하면서 CPU는 90% 나 잡아먹는 상태가 무한히 계속되는 문제가 발생할 수 있다. 스트림 라이브러리가 이 파이프라인을 병렬화하는 방법을 찾아내지 못했기 때문이다.

  • 데이터 소스가 Stream.iterate거나 중간 연산으로 limit를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.

사용해도 좋은 경우

스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 병렬화의 효과가 가장 좋다.

이 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 일을 다수의 스레드에 분배하기에 좋다. 또한 원소들을 순차적으로 실행할 때의 참조 지역성이 좋다.

또한, 종단 연산에서 reduce, anyMatch, allMatch, noneMatch 등과 같이

  • 파이프라인의 원소를 하나로 합치는 경우
  • 조건에 맞으면 반환하는 경우 에도 병렬화에 적합하다.

하지만 위와 같은 조건을 만족한다고 하더라도, 파이프라인이 수행하는 진짜 작업이 병렬화에 드는 추가 비용을 상쇄하지 못한다면 실제 성능 향상은 미미할 수 있다. 따라서 병렬화를 고려할 때는 성능 지표 또한 고려하도록 하자.

Leave a comment