48. 스트림 병렬화는 주의해서 적용하라.

무한성장개발자·2025년 9월 25일

package me.whiteship.chapter07.item48;

import java.math.BigInteger;
import java.util.stream.Stream;

import static java.math.BigInteger.ONE;
import static java.math.BigInteger.TWO;

// 병렬 스트림을 사용해 처음 20개의 "메르센 소수"를 생성하는 프로그램 (291쪽 코드 48-1의 병렬화 버전)
// 주의: 병렬화의 영향으로 프로그램이 종료하지 않는다.
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);
    }
}

limit이나 iterate를 Serializable이 필요하기 때문에 parallel이 적절하지 않다.

limit은 전체를 순환한 후에 가져오기 때문에 너무 느리다.
병렬적으로 분해되는 데이터 소스가 아니다.
그래서 parallel을 잘못 쓰면 더 느려진다.

array는 데이터가 모여있어서 가장 빠르다.
int의 범위, long 범위를 쓸 때 최적화 하기 좋다.
중간 연산에는 결합법칙에 만족해야 하고 간섭받지 말아야 한다.
결합법칙은
// (a op b) op c == a op (b op c)
// (a + b) + c == a + (b + c)

// (a - b) - c != a - (b - c)

파이프라인을 수행하는 도중에 데이터가 변경되지 말아야 한다.
collect는 결과에 해당하는 결과값이 바뀌는 것이다.

package me.whiteship.chapter07.item48;

import java.math.BigInteger;
import java.util.stream.LongStream;

public class ParallelPrimeCounting {
    // 코드 48-3 소수 계산 스트림 파이프라인 - 병렬화 버전 (295쪽)
    static long pi(long n) {
        return LongStream.rangeClosed(2, n)
                .parallel()
                .mapToObj(BigInteger::valueOf)
                .filter(i -> i.isProbablePrime(50))
                .count();
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        System.out.println(start);
        System.out.println(pi(10_000_000));
        System.out.println(System.currentTimeMillis() - start);
    }
}

2부터 100만까지는 적절한 범위이다.

count는 적절한 종단 연산이다.

완벽 공략 53. Spliterator

Spliterator == Splitting + Iterator

package me.whiteship.chapter07.item48.spliterator;

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

public class SpliteratorExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "cherry", "date");

        Spliterator<String> spliterator = list.spliterator();

        System.out.println("spliterator.tryAdvance:");
        spliterator.tryAdvance(System.out::println);

        System.out.println("spliterator.forEachRemaining:");
        spliterator.forEachRemaining(System.out::println);
    }
}

tryAdvance는 뒤에 있는 함수를 한번만 적용해보는 것이다.

그 다음부터 처리하고 싶으면 banana부터 출력이 된다.

package me.whiteship.chapter07.item48.spliterator;

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

public class SpliteratorTrySplitExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "cherry", "date", "elderberry", "fig", "grape");

        Spliterator<String> spliterator1 = list.spliterator();
        Spliterator<String> spliterator2 = spliterator1.trySplit();

        System.out.println("Spliterator 1:");
        spliterator1.forEachRemaining(System.out::println);

        System.out.println("Spliterator 2:");
        if (spliterator2 != null) {
            spliterator2.forEachRemaining(System.out::println);
        }
    }
}

trySplit일 때 null이 나올 수도 있다.

package me.whiteship.chapter07.item48.spliterator;

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

public class SpliteratorCharacteristicsExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "cherry", "date");

        Spliterator<String> spliterator = list.spliterator();

        // Check and print characteristics
        int characteristics = spliterator.characteristics();
        System.out.println("Characteristics: " + characteristics);
//        System.out.println("Characteristics: " + Integer.toBinaryString(characteristics));

        // Check if the Spliterator is ORDERED
        if ((characteristics & Spliterator.ORDERED) != 0) {
            System.out.println("Spliterator is ORDERED");
        }

//        if (spliterator.hasCharacteristics(Spliterator.ORDERED)) {
//            System.out.println("Spliterator is ORDERED");
//        }

        // Check if the Spliterator is DISTINCT
        if ((characteristics & Spliterator.DISTINCT) != 0) {
            System.out.println("Spliterator is DISTINCT");
        }

        // Check if the Spliterator is SORTED
        if ((characteristics & Spliterator.SORTED) != 0) {
            System.out.println("Spliterator is SORTED");
        }

        // Check if the Spliterator is SIZED
        if ((characteristics & Spliterator.SIZED) != 0) {
            System.out.println("Spliterator is SIZED");
        }

        // Check if the Spliterator is NONNULL
        if ((characteristics & Spliterator.NONNULL) != 0) {
            System.out.println("Spliterator is NONNULL");
        }

        // Check if the Spliterator is IMMUTABLE
        if ((characteristics & Spliterator.IMMUTABLE) != 0) {
            System.out.println("Spliterator is IMMUTABLE");
        }

        // Check if the Spliterator is CONCURRENT
        if ((characteristics & Spliterator.CONCURRENT) != 0) {
            System.out.println("Spliterator is CONCURRENT");
        }

        // Check if the Spliterator is SUBSIZED
        if ((characteristics & Spliterator.SUBSIZED) != 0) {
            System.out.println("Spliterator is SUBSIZED");
        }
    }
}

Charateristic은 가져와서 객체의 특징을 가지고 오는 것이다.

parallel에서 spliter를 사용하고 있다. 그리고 이러한 메소드들이 있다.

완벽 공략 54. 랜덤

package me.whiteship.chapter07.item48.random;

import java.util.Random;
import java.util.SplittableRandom;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;

public class RandomExample {
    public static void main(String[] args) {
        // Using Random
        Random random = new Random();
        System.out.println("Random: "  + random.nextInt(100));

        // Using ThreadLocalRandom
        int randomValue = ThreadLocalRandom.current().nextInt(100);
        System.out.println("ThreadLocalRandom: " + randomValue);

        // Using SplittableRandom
        SplittableRandom splittableRandom = new SplittableRandom();
        IntStream randomIntStream = splittableRandom.ints(10, 0, 100);
        long count = randomIntStream
                .parallel()
                .filter(number -> number > 50)
                .count();
        System.out.println(count);
    }
}

멀티스레드 환경에서는 Random이 적합하지는 않다. threadsafe는 해서 문제는 없는데 성능 상 안좋다.
synchronized 되어있어서 느리다.

멀티스레드에서는 ThreadLocalRandom이 좋다.

splittableRandom은 threadsafe하지 않다. stream을 만들어서 병렬처리를 하기 위한 random이다.

0개의 댓글