자바의 동시성 프로그래밍
- 첫 릴리스에서부터 스레드, 동기화, wait/notify를 지원했다.
- 자바 5부터는 동시성 컬렉션인 java.util.concurrent 라이브러리와 실행자 프레임워크를 지원했다.
- 자바 7부터는 고성능 병렬 분해 프레임워크 포크-조인 패키지를 추가했다.
- 자바 8부터는 parallel 메서드만 한 번 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원했다.
동시성 프로그래밍에서는 안전성과 응답 기능 상태를 유지하는 것이 중요한 포인트인데, 자바의 많은 지원에도 올바르고 빠르게 작성하는 일은 여전히 어렵다.
병렬 스트림의 잘못된 사용
다음은 스트림을 사용해 처음 20개의 메르센 소수를 생성하는 프로그램이다.
public static void main(String[]args){
primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
.filter(mersenne -> mersenne.isProbablePrime(50)
.limit(20)
.forEach(System.out::println);
}
static Stream<BigInteger> primes() {
return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
이 프로그램의 속도를 높이고 싶어 parallel()을 호출하면 CPU만 잡아먹고 결과는 출력하지 않는다. 원인은 스트림 라이브러리가 이 파이프라인을 병렬화하는 방법을 찾아내지 못했기 때문이다. 환경이 아무리 좋더라도 데이터 소스가 Stream.iterate거나 중간 연산으로 limit을 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
-> why ? 파이프라인 병렬화는 limit을 다룰 때 CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다. 즉, 위의 코드에서 19개의 메르센 소수를 찾고 20번째 계산이 수행되는 시점에 남은 코어가 21, 22, 23번째 메르센 소수를 찾는 작업이 병렬로 수행되고 이러한 작업은 20번째 계산보다 2배, 4배, 8배의 시간이 든다.
병렬 스트림의 대표적 사용
스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int범위, long범위일 때 병렬화의 효과가 가장 좋다.
그 이유는 다음과 같다.
- 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어 일을 다수의 스레드에 분배하기 좋다.
-> 나누는 작업은 Spliterator가 담당하며, Spliterator 객체는 Stream이나 Iterable의 spliterator 메서드로 얻을 수 있다. - 원소들을 순차적으로 실행할 때의 참조 지역성이 뛰어나다. 즉, 이웃한 원소의 참조들이 메모리에 연속해서 저장되어 있다. 참조들이 가리키는 실제 객채가 메모리에서 떨어져 있는 경우 스레드는 데이터가 주 메모리에서 캐시 메모리로 전송되어 오기를 기다리며 시간을 날리기 때문에 참조 지역성은 다량의 데이터를 처리하는 벌크 연산을 병렬화할 때 아주 중요한 요소로 작용한다.
- 종단 연산 중 병렬화에 가장 적합한 것은 축소이다. 축소는 파이프라인에서 만들어진 모든 원소를 하나로 합치는 작업으로, Stream의 reduce 메서드 중 하나, 혹은 min, max, count, sum 같이 완성된 형태로 제공되는 메서드 중 하나를 선택해 수행한다. anyMatch, allMatch, noneMatch처럼 조건에 맞으면 바로 반환되는 메서드도 병렬화에 적합하다. 반면에 가변 축소를 수행하는 collect 메서드는 병렬화에 적합하지 않다.
성능 추정을 하는 간단한 방법
스트림 안의 원소 수 * 원소당 수행되는 코드 줄 수 >= 수십만이 되어야 성능 향상을 맛볼 수 있다.
ThreadLocalRandom, SplittableRandom
무작위 수들로 이뤄진 스트림을 병렬화하려거든 ThreadLocalRandom또는 SplittableRandom인스턴스를 이용하자. 그냥 Random은 모든 연산을 동기화하기 때문에 병렬 처리하면 최악의 성능을 보인다.