Использует ли Parallel Stream собственный пул потоков?

«Использует ли Parallel Stream собственный пул потоков?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, Parallel Stream по умолчанию использует общий ForkJoinPool.

1. Общий пул (ForkJoinPool.commonPool()):

  • Размер: Runtime.getRuntime().availableProcessors() - 1
  • Общий для всех Parallel Stream в JVM
  • Может привести к contention при одновременном выполнении многих параллельных операций

2. Пример использования общего пула:

List<Integer> numbers = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toList());

// Использует commonPool
long count = numbers.parallelStream()
    .filter(n -> n % 2 == 0)
    .count();

System.out.println("Parallelism: " + ForkJoinPool.commonPool().getParallelism());

3. Использование кастомного пула:

ForkJoinPool customPool = new ForkJoinPool(4); // 4 потока

try {
    List<String> result = customPool.submit(() -> 
        largeList.parallelStream()
            .map(item -> processItem(item)) // CPU-intensive операция
            .filter(Objects::nonNull)
            .collect(Collectors.toList())
    ).get(); // Блокирующий вызов
} finally {
    customPool.shutdown();
}

4. Когда использовать кастомный пул:

  • Длительные CPU-bound задачи, чтобы не блокировать commonPool.
  • Изоляция критичных по времени операций.
  • Контроль приоритетов разных типов задач.

5. Best Practices для Parallel Stream:

// ПЛОХО: Мелкие задачи - накладные расходы
list.parallelStream().map(x -> x + 1).collect(Collectors.toList());

// ХОРОШО: Тяжелые вычисления
list.parallelStream()
    .map(item -> {
        // Тяжелая операция (> 1ms)
        return expensiveCalculation(item);
    })
    .collect(Collectors.toList());

// ПЛОХО: Блокирующие операции (I/O)
urls.parallelStream()
    .map(url -> download(url)) // Блокирующий вызов
    .collect(Collectors.toList());

// ХОРОШО: Использование CompletableFuture для I/O
List<CompletableFuture<String>> futures = urls.stream()
    .map(url -> CompletableFuture.supplyAsync(() -> download(url), ioExecutor))
    .collect(Collectors.toList());

6. Альтернативы для I/O операций:

  • CompletableFuture с отдельным ExecutorService
  • Реактивные стримы (Project Reactor, RxJava)
  • Виртуальные потоки (Java 21+)