Java 8 병렬 스트림의 사용자 정의 스레드 풀


398

Java 8 병렬 스트림에 대한 사용자 정의 스레드 풀을 지정할 수 있습니까? 어디서나 찾을 수 없습니다.

서버 응용 프로그램이 있고 병렬 스트림을 사용하고 싶다고 가정하십시오. 그러나 응용 프로그램은 크고 멀티 스레드이므로 구획화하고 싶습니다. 한 모듈의 응용 프로그램에서 다른 모듈의 작업을 느리게 실행하고 싶지 않습니다.

다른 모듈에 대해 다른 스레드 풀을 사용할 수 없으면 대부분의 실제 상황에서 병렬 스트림을 안전하게 사용할 수 없습니다.

다음 예제를 시도하십시오. 별도의 스레드에서 실행되는 CPU 집약적 작업이 있습니다. 작업은 병렬 스트림을 활용합니다. 첫 번째 작업이 중단되었으므로 각 단계는 1 초가 걸립니다 (스레드 절전으로 시뮬레이션 됨). 문제는 다른 스레드가 멈추고 깨진 작업이 끝날 때까지 기다리는 것입니다. 이것은 예를 들어 설명되었지만 서블릿 앱과 누군가가 장기 실행 작업을 공유 포크 조인 풀에 제출한다고 가정합니다.

public class ParallelTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        es.execute(() -> runTask(1000)); //incorrect task
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));


        es.shutdown();
        es.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static void runTask(int delay) {
        range(1, 1_000_000).parallel().filter(ParallelTest::isPrime).peek(i -> Utils.sleep(delay)).max()
                .ifPresent(max -> System.out.println(Thread.currentThread() + " " + max));
    }

    public static boolean isPrime(long n) {
        return n > 1 && rangeClosed(2, (long) sqrt(n)).noneMatch(divisor -> n % divisor == 0);
    }
}

3
커스텀 스레드 풀은 무엇을 의미합니까? 하나의 공통 ForkJoinPool이 있지만 언제든지 자신의 ForkJoinPool을 작성하여 요청을 제출할 수 있습니다.
초에 edharned

7
힌트 : Java Champion Heinz Kabutz는 동일한 문제를 검사하지만 더 큰 영향을줍니다. 공통 포크 조인 풀의 교착 상태 스레드. 참조 javaspecialists.eu/archive/Issue223.html
Peti

답변:


395

실제로 특정 포크 조인 풀에서 병렬 작업을 실행하는 방법이 있습니다. 포크 조인 풀에서 작업으로 실행하면 그대로 유지되며 일반적인 것을 사용하지 않습니다.

final int parallelism = 4;
ForkJoinPool forkJoinPool = null;
try {
    forkJoinPool = new ForkJoinPool(parallelism);
    final List<Integer> primes = forkJoinPool.submit(() ->
        // Parallel task here, for example
        IntStream.range(1, 1_000_000).parallel()
                .filter(PrimesPrint::isPrime)
                .boxed().collect(Collectors.toList())
    ).get();
    System.out.println(primes);
} catch (InterruptedException | ExecutionException e) {
    throw new RuntimeException(e);
} finally {
    if (forkJoinPool != null) {
        forkJoinPool.shutdown();
    }
}

이 트릭은 ForkJoinTask.fork 를 기반으로 합니다. "해당되는 경우 현재 작업이 실행중인 풀에서 또는 해당 ForkJoinPool ()이 아닌 경우 ForkJoinPool.commonPool ()을 사용하여 풀에서이 작업을 비동기식으로 실행하도록 구성합니다."


20
솔루션에 대한 자세한 내용은 blog.krecan.net/2014/03/18/…
Lukas

3
그러나 스트림 ForkJoinPool이 구현 세부 사항을 사용 하거나 구현 세부 사항을 사용하도록 지정되어 있습니까? 설명서에 대한 링크가 좋을 것입니다.
Nicolai

6
@Lukas 스 니펫 주셔서 감사합니다. 스레드 누수를 피하기 위해 더 이상 필요하지 않은 경우 ForkJoinPool인스턴스 를 추가 shutdown()할 것입니다. (예)
jck

5
Java 8에는 작업이 사용자 정의 풀 인스턴스에서 실행되고 있지만 여전히 공유 풀에 연결되어 있다는 버그가 있습니다. 계산 크기는 사용자 정의 풀이 아닌 공통 풀에 비례합니다. Java 10에서 수정되었습니다 : JDK-8190974
Terran

3
@terran이 문제는 자바 8 수정되었습니다 bugs.openjdk.java.net/browse/JDK-8224620
Cutberto 오캄포에게

192

병렬 스트림은 기본 사용 하면 프로세서가 기본적으로 하나 개의 적은 스레드가을 에 의해 반환, (그들은 또한 메인 스레드를 사용하기 때문에 병렬 스트림의 모든 프로세서를 사용하는 것이이 방법을) :ForkJoinPool.commonPoolRuntime.getRuntime().availableProcessors()

별도 또는 사용자 지정 풀이 필요한 응용 프로그램의 경우 지정된 대상 병렬 수준으로 ForkJoinPool을 구성 할 수 있습니다. 기본적으로 사용 가능한 프로세서 수와 같습니다.

또한 중첩 된 병렬 스트림 또는 여러 병렬 스트림이 동시에 시작된 경우 모두 동일한 풀을 공유 합니다. 장점 : 기본값 (사용 가능한 프로세서 수) 이상을 사용하지 마십시오. 단점 : 시작한 각 병렬 스트림에 "모든 프로세서"가 할당되지 않을 수 있습니다 (하나 이상이있는 경우). 분명히 ManagedBlocker 를 사용하여 이를 피할 수 있습니다.

병렬 스트림이 실행되는 방식을 변경하려면 다음 중 하나를 수행하십시오.

  • 병렬 스트림 실행을 자신의 ForkJoinPool에 제출하십시오. yourFJP.submit(() -> stream.parallel().forEach(soSomething)).get();또는
  • System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20")20 개 스레드의 대상 병렬 처리에 대해 시스템 특성을 사용하여 공통 풀의 크기를 변경할 수 있습니다 . 그러나 백 포트 패치 https://bugs.openjdk.java.net/browse/JDK-8190974 후에는 더 이상 작동하지 않습니다 .

프로세서가 8 개인 내 컴퓨터의 예입니다. 다음 프로그램을 실행하면

long start = System.currentTimeMillis();
IntStream s = IntStream.range(0, 20);
//System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
s.parallel().forEach(i -> {
    try { Thread.sleep(100); } catch (Exception ignore) {}
    System.out.print((System.currentTimeMillis() - start) + " ");
});

출력은 다음과 같습니다.

215216216216216216216216216315316316316316316316316415416416416416

따라서 병렬 스트림이 한 번에 8 개의 항목을 처리한다는 것을 알 수 있습니다. 즉, 8 개의 스레드를 사용합니다. 그러나 주석 처리 된 행의 주석을 해제하면 출력은 다음과 같습니다.

215215215215215215216216216216216216216216216216216216216216216216216

이번에는 병렬 스트림이 20 개의 스레드를 사용했으며 스트림의 20 개 요소가 모두 동시에 처리되었습니다.


30
(가) commonPool하나보다 실제로이 availableProcessors총 병렬 처리의 결과는 동일 availableProcessors하나 호출 스레드 수가 있기 때문이다.
Marko Topolnik

2
수익을 제출 ForkJoinTask. 모방이 parallel() get()필요합니다 :stream.parallel().forEach(soSomething)).get();
그리고 리 Kislin을

5
ForkJoinPool.submit(() -> stream.forEach(...))지정된 스트림 작업을 실행할 것이라고 확신하지 않습니다 ForkJoinPool. 전체 Stream-Action이 ForJoinPool에서 ONE 작업으로 실행되지만 내부적으로 여전히 기본 / 공통 ForkJoinPool을 사용하고 있습니다. ForkJoinPool.submit ()은 당신이하는 말을 어디에서 할 것입니까?
Frederic Leitenberger

@FredericLeitenberger 아마도 당신은 Lukas의 답변 아래에 귀하의 의견을 제시하려고했을 것입니다.
assylias

2
이제 stackoverflow.com/a/34930831/1520422가 실제로 발표 된대로 작동한다는 것을 알 수 있습니다. 그러나 나는 여전히 그것이 어떻게 작동하는지 이해하지 못한다. 그러나 나는 "작동합니다"괜찮습니다. 감사!
Frederic Leitenberger

39

자신의 forkJoinPool 내에서 병렬 계산을 트리거하는 트릭 대신 다음과 같이 해당 풀을 CompletableFuture.supplyAsync 메서드로 전달할 수도 있습니다.

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
CompletableFuture<List<Integer>> primes = CompletableFuture.supplyAsync(() ->
    //parallel task here, for example
    range(1, 1_000_000).parallel().filter(PrimesPrint::isPrime).collect(toList()), 
    forkJoinPool
);

22

원래 솔루션 (ForkJoinPool 공통 병렬 처리 속성 설정)이 더 이상 작동하지 않습니다. 원래 답변의 링크를 보면이 문제를 해결하는 업데이트가 Java 8로 다시 포팅되었습니다. 링크 된 스레드에서 언급 했듯이이 솔루션은 영원히 작동하지 않을 수 있습니다. 이를 바탕으로 솔루션은 forkjoinpool.submit with .get 솔루션으로 허용되는 답변에 설명되어 있습니다. 백 포트가이 솔루션의 신뢰성도 해결한다고 생각합니다.

ForkJoinPool fjpool = new ForkJoinPool(10);
System.out.println("stream.parallel");
IntStream range = IntStream.range(0, 20);
fjpool.submit(() -> range.parallel()
        .forEach((int theInt) ->
        {
            try { Thread.sleep(100); } catch (Exception ignore) {}
            System.out.println(Thread.currentThread().getName() + " -- " + theInt);
        })).get();
System.out.println("list.parallelStream");
int [] array = IntStream.range(0, 20).toArray();
List<Integer> list = new ArrayList<>();
for (int theInt: array)
{
    list.add(theInt);
}
fjpool.submit(() -> list.parallelStream()
        .forEach((theInt) ->
        {
            try { Thread.sleep(100); } catch (Exception ignore) {}
            System.out.println(Thread.currentThread().getName() + " -- " + theInt);
        })).get();

ForkJoinPool.commonPool().getParallelism()디버그 모드 에서 병렬 처리가 변경되는 것을 볼 수 없습니다 .
d-coder

감사. 나는 테스트 / 연구를하고 답변을 업데이트했습니다. 이전 버전에서 작동하므로 업데이트가 변경된 것처럼 보입니다.
Tod Casasent 2016 년

루프에서 unreported exception InterruptedException; must be caught or declared to be thrown모든 catch예외가 발생 하더라도이 문제가 계속 발생하는 이유는 무엇입니까?
Rocky Li

록키, 나는 오류가 표시되지 않습니다. Java 버전과 정확한 줄을 아는 것이 도움이 될 것입니다. "InterruptedException"은 사용중인 버전에서 수면 주변의 try / catch가 올바르게 닫히지 않았 음을 나타냅니다.
Tod Casasent

13

다음 속성을 사용하여 기본 병렬 처리를 변경할 수 있습니다.

-Djava.util.concurrent.ForkJoinPool.common.parallelism=16

더 많은 병렬 처리를 사용하도록 설정할 수 있습니다.


전역 설정이지만 parallelStream을 높이기 위해 작동합니다.
meadlai

이것은 openjdk 버전 "1.8.0_222"에서 나를 위해 일했습니다.
압바스

상기와 같은 사람은이 오픈 JDK "11.0.6"에 나를 위해 작동하지 않습니다
압바스

8

사용 된 스레드의 실제 수를 측정하려면 다음을 확인하십시오 Thread.activeCount().

    Runnable r = () -> IntStream
            .range(-42, +42)
            .parallel()
            .map(i -> Thread.activeCount())
            .max()
            .ifPresent(System.out::println);

    ForkJoinPool.commonPool().submit(r).join();
    new ForkJoinPool(42).submit(r).join();

이는 4 코어 CPU에서 다음과 같은 출력을 생성 할 수 있습니다.

5 // common pool
23 // custom pool

.parallel()그것 없이는 :

3 // common pool
4 // custom pool

6
Thread.activeCount ()는 스트림을 처리하는 스레드를 알려주지 않습니다. 대신 Thread.currentThread (). getName ()에 매핑 한 다음 distinct ()를 지정하십시오. 그러면 풀의 모든 스레드가 사용되지는 않습니다. 처리 지연을 추가하면 풀의 모든 스레드가 사용됩니다.
keyoxy

7

지금까지 나는이 질문에 대한 답변에 설명 된 솔루션을 사용했습니다. 이제 병렬 스트림 지원 이라는 작은 라이브러리를 생각해 냈습니다 .

ForkJoinPool pool = new ForkJoinPool(NR_OF_THREADS);
ParallelIntStreamSupport.range(1, 1_000_000, pool)
    .filter(PrimesPrint::isPrime)
    .collect(toList())

그러나 @PabloMatiasGomez가 주석에서 지적했듯이 공통 스트림의 크기에 크게 의존하는 병렬 스트림의 분할 메커니즘에 대한 단점이 있습니다. HashSet의 병렬 스트림이 병렬로 실행되지 않음을 참조하십시오 .

이 솔루션을 다른 유형의 작업에 대해 별도의 풀을 갖기 위해 사용하고 있지만 사용하지 않더라도 공통 풀의 크기를 1로 설정할 수 없습니다.



1

풀 크기를 조정하기 위해 다음과 같이 사용자 정의 ForkJoinPool을 시도했습니다 .

private static Set<String> ThreadNameSet = new HashSet<>();
private static Callable<Long> getSum() {
    List<Long> aList = LongStream.rangeClosed(0, 10_000_000).boxed().collect(Collectors.toList());
    return () -> aList.parallelStream()
            .peek((i) -> {
                String threadName = Thread.currentThread().getName();
                ThreadNameSet.add(threadName);
            })
            .reduce(0L, Long::sum);
}

private static void testForkJoinPool() {
    final int parallelism = 10;

    ForkJoinPool forkJoinPool = null;
    Long result = 0L;
    try {
        forkJoinPool = new ForkJoinPool(parallelism);
        result = forkJoinPool.submit(getSum()).get(); //this makes it an overall blocking call

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    } finally {
        if (forkJoinPool != null) {
            forkJoinPool.shutdown(); //always remember to shutdown the pool
        }
    }
    out.println(result);
    out.println(ThreadNameSet);
}

풀이 기본 4 보다 많은 스레드를 사용하고 있다는 출력이 있습니다 .

50000005000000
[ForkJoinPool-1-worker-8, ForkJoinPool-1-worker-9, ForkJoinPool-1-worker-6, ForkJoinPool-1-worker-11, ForkJoinPool-1-worker-10, ForkJoinPool-1-worker-1, ForkJoinPool-1-worker-15, ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-4, ForkJoinPool-1-worker-2]

그러나 실제로 다음과 같이 사용하여 동일한 결과를 얻으려고 할 때 weirdoThreadPoolExecutor있습니다.

BlockingDeque blockingDeque = new LinkedBlockingDeque(1000);
ThreadPoolExecutor fixedSizePool = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, blockingDeque, new MyThreadFactory("my-thread"));

그러나 나는 실패했다.

새 스레드에서 parallelStream 만 시작하고 다른 모든 항목은 동일 하므로 ForkJoinPool 을 사용 하여 자식 스레드를 시작 함 을 다시 나타냅니다 .parallelStream


다른 실행 프로그램을 허용하지 않는 이유는 무엇입니까?
omjego 2016 년

@omjego 좋은 질문 일 것입니다. 새로운 질문을 시작하고 아이디어를 구체화하기위한 자세한 내용을 제공 할 수 있습니다.)
Hearen

1

AbacusUtil로 이동 하십시오 . 병렬 스트림에 대해 스레드 번호를 지정할 수 있습니다. 샘플 코드는 다음과 같습니다.

LongStream.range(4, 1_000_000).parallel(threadNum)...

공개 : 저는 AbacusUtil의 개발자입니다.


1

구현 해킹에 의존하고 싶지 않다면 결합 mapcollect의미론 을 결합하는 사용자 정의 수집기를 구현하여 동일한 결과를 얻는 방법이 항상 있습니다.

list.stream()
  .collect(parallelToList(i -> fetchFromDb(i), executor))
  .join()

운 좋게도 이미 여기에서 완료되었으며 Maven Central에서 사용할 수 있습니다. http://github.com/pivovarit/parallel-collectors

면책 조항 : 나는 그것을 작성하고 책임을집니다.


0

cyclops-react 와 함께 타사 라이브러리를 사용하는 것이 마음에 들지 않으면 동일한 파이프 라인 내에서 순차적 스트림과 병렬 스트림을 혼합하고 사용자 정의 ForkJoinPools를 제공 할 수 있습니다. 예를 들어

 ReactiveSeq.range(1, 1_000_000)
            .foldParallel(new ForkJoinPool(10),
                          s->s.filter(i->true)
                              .peek(i->System.out.println("Thread " + Thread.currentThread().getId()))
                              .max(Comparator.naturalOrder()));

또는 순차적 스트림 내에서 처리를 계속하려는 경우

 ReactiveSeq.range(1, 1_000_000)
            .parallel(new ForkJoinPool(10),
                      s->s.filter(i->true)
                          .peek(i->System.out.println("Thread " + Thread.currentThread().getId())))
            .map(this::processSequentially)
            .forEach(System.out::println);

[공개 나는 사이클롭스 반응의 주요 개발자입니다]


0

사용자 정의 ThreadPool이 필요하지 않지만 동시 작업 수를 제한하려는 경우 다음을 사용할 수 있습니다.

List<Path> paths = List.of("/path/file1.csv", "/path/file2.csv", "/path/file3.csv").stream().map(e -> Paths.get(e)).collect(toList());
List<List<Path>> partitions = Lists.partition(paths, 4); // Guava method

partitions.forEach(group -> group.parallelStream().forEach(csvFilePath -> {
       // do your processing   
}));

(이를 묻는 중복 질문이 잠겨 있으므로 여기로 보내주세요.)


-2

이 ForkJoinWorkerThreadFactory를 구현하여 Fork-Join 클래스에 삽입 할 수 있습니다.

public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

이 포크 조인 풀 생성자를 사용하여이 작업을 수행 할 수 있습니다.

참고 :-1. 이것을 사용하는 경우 새 스레드 구현을 기반으로 JVM 스케줄링에 영향을 미치며, 일반적으로 포크 조인 스레드를 다른 코어 (계산 스레드로 처리)로 스케줄합니다. 2. 스레드에 대한 포크 조인에 의한 작업 스케줄링은 영향을받지 않습니다. 3. 병렬 스트림이 포크 조인에서 스레드를 선택하는 방법을 실제로 알지 못했기 때문에 (적절한 문서를 찾을 수 없음) 다른 스레드 이름 지정 팩토리를 사용하여 병렬 스트림의 스레드가 선택되는지 확인하십시오. 제공하는 customThreadFactory에서. 4. commonThreadPool은이 customThreadFactory를 사용하지 않습니다.


지정한 방법을 사용하는 방법을 보여주는 유용한 예를 제공 할 수 있습니까?
J. 머레이
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.