← Назад к вопросам

В чем разница между Stream и ParallelStream?

2.0 Middle🔥 111 комментариев
#Stream API и функциональное программирование#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Различие между Stream и ParallelStream

Stream и ParallelStream — это два способа обработки коллекций в Java с использованием функционального подхода. Stream обрабатывает элементы последовательно (в одном потоке), а ParallelStream использует несколько потоков для параллельной обработки. Выбор между ними критичен для производительности приложения.

Stream — Последовательная обработка

Обычный Stream выполняет операции в одном потоке, один элемент за раз:

import java.util.*;
import java.util.stream.*;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Последовательный Stream
        Stream<Integer> stream = numbers.stream();
        
        // Обработка элементов последовательно
        List<Integer> squared = stream
            .map(n -> {
                System.out.println("Processing: " + n + 
                    " on thread " + Thread.currentThread().getName());
                return n * n;
            })
            .collect(Collectors.toList());
        
        System.out.println("Result: " + squared);
    }
}

// Вывод:
// Processing: 1 on thread main
// Processing: 2 on thread main
// Processing: 3 on thread main
// ... все операции в main потоке

ParallelStream — Параллельная обработка

ParallelStream использует Fork/Join framework для распараллеливания работы:

import java.util.*;
import java.util.stream.*;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Параллельный Stream
        List<Integer> squared = numbers.parallelStream()
            .map(n -> {
                System.out.println("Processing: " + n + 
                    " on thread " + Thread.currentThread().getName());
                return n * n;
            })
            .collect(Collectors.toList());
        
        System.out.println("Result: " + squared);
    }
}

// Вывод (порядок может отличаться):
// Processing: 1 on thread ForkJoinPool.commonPool-worker-1
// Processing: 3 on thread ForkJoinPool.commonPool-worker-2
// Processing: 5 on thread ForkJoinPool.commonPool-worker-3
// ... операции выполняются в разных потоках

Основные различия

1. Количество потоков

public class ThreadCountExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        
        // Stream — 1 поток (main)
        System.out.println("Stream:");
        list.stream()
            .forEach(n -> System.out.println(
                "Thread: " + Thread.currentThread().getName()));
        
        System.out.println("\nParallelStream:");
        // ParallelStream — несколько потоков
        list.parallelStream()
            .forEach(n -> System.out.println(
                "Thread: " + Thread.currentThread().getName()));
    }
}

2. Порядок выполнения

Stream гарантирует порядок, ParallelStream — нет:

public class OrderExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Stream сохраняет порядок
        System.out.println("Stream order:");
        numbers.stream()
            .forEach(System.out::print); // 1 2 3 4 5
        
        System.out.println("\nParallelStream order:");
        // ParallelStream может не сохранять порядок вывода
        numbers.parallelStream()
            .forEach(System.out::print); // может быть: 3 1 5 2 4
        
        // Но forEachOrdered гарантирует порядок
        System.out.println("\nParallelStream with order:");
        numbers.parallelStream()
            .forEachOrdered(System.out::print); // 1 2 3 4 5
    }
}

Когда использовать Stream

Stream предпочтительнее в следующих ситуациях:

// 1. Маленькие коллекции
public class SmallCollectionExample {
    public static void main(String[] args) {
        List<Integer> small = Arrays.asList(1, 2, 3);
        
        // Stream быстрее благодаря меньшим накладным расходам
        small.stream()
            .map(n -> n * 2)
            .forEach(System.out::println);
    }
}

// 2. Простые операции
public class SimpleOperationExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("hello", "world");
        
        // Простая фильтрация лучше в Stream
        words.stream()
            .filter(w -> w.length() > 3)
            .forEach(System.out::println);
    }
}

// 3. I/O операции
public class IOExample {
    public static void main(String[] args) throws Exception {
        List<String> lines = Arrays.asList("line1", "line2");
        
        // I/O operáций лучше в обычном Stream
        lines.stream()
            .forEach(line -> {
                try {
                    // Запись в файл
                    System.out.println("Writing: " + line);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
    }
}

Когда использовать ParallelStream

ParallelStream эффективнее в следующих случаях:

// 1. Большие коллекции (>10000 элементов)
public class LargeCollectionExample {
    public static void main(String[] args) {
        List<Integer> large = new ArrayList<>();
        for (int i = 0; i < 100_000; i++) {
            large.add(i);
        }
        
        // ParallelStream выигрывает на больших данных
        long startTime = System.currentTimeMillis();
        large.parallelStream()
            .map(n -> n * n)
            .filter(n -> n % 2 == 0)
            .count();
        long parallelTime = System.currentTimeMillis() - startTime;
        
        System.out.println("ParallelStream time: " + parallelTime + "ms");
    }
}

// 2. Дорогие вычисления
public class ExpensiveComputationExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Сложные вычисления выигрывают от параллелизма
        long startTime = System.currentTimeMillis();
        List<Integer> result = numbers.parallelStream()
            .map(n -> complexCalculation(n))
            .collect(Collectors.toList());
        long parallelTime = System.currentTimeMillis() - startTime;
        
        System.out.println("Result: " + result);
        System.out.println("Time: " + parallelTime + "ms");
    }
    
    private static int complexCalculation(int n) {
        int result = n;
        for (int i = 0; i < 100_000_000; i++) {
            result = (result * result) % 1_000_000;
        }
        return result;
    }
}

// 3. CPU-интенсивные операции
public class CPUIntensiveExample {
    public static void main(String[] args) {
        List<Long> numbers = Arrays.asList(1L, 2L, 3L, 4L, 5L);
        
        // CPU-bound задачи эффективнее в ParallelStream
        numbers.parallelStream()
            .map(n -> calculatePrimes(n))
            .forEach(System.out::println);
    }
    
    private static long calculatePrimes(long n) {
        long count = 0;
        for (long i = 2; i <= n; i++) {
            if (isPrime(i)) count++;
        }
        return count;
    }
    
    private static boolean isPrime(long n) {
        if (n < 2) return false;
        for (long i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
}

Производительность: Stream vs ParallelStream

import java.util.*;
import java.util.stream.*;

public class PerformanceComparison {
    public static void main(String[] args) {
        // Создаём большую коллекцию
        List<Integer> numbers = new ArrayList<>();
        for (int i = 1; i <= 100_000; i++) {
            numbers.add(i);
        }
        
        // Stream
        long startStream = System.nanoTime();
        long resultStream = numbers.stream()
            .filter(n -> n % 2 == 0)
            .map(n -> n * 2)
            .count();
        long streamTime = System.nanoTime() - startStream;
        
        // ParallelStream
        long startParallel = System.nanoTime();
        long resultParallel = numbers.parallelStream()
            .filter(n -> n % 2 == 0)
            .map(n -> n * 2)
            .count();
        long parallelTime = System.nanoTime() - startParallel;
        
        System.out.println("Stream time: " + streamTime / 1_000_000 + "ms");
        System.out.println("ParallelStream time: " + parallelTime / 1_000_000 + "ms");
        System.out.println("Результаты одинаковы: " + (resultStream == resultParallel));
    }
}

Практические советы

1. Избегай ParallelStream с I/O

// ❌ ПЛОХО: I/O в ParallelStream
public class BadIOExample {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList("url1", "url2", "url3");
        
        // Плохо: I/O операции не выигрывают от параллелизма
        urls.parallelStream()
            .map(url -> fetchData(url))
            .forEach(System.out::println);
    }
    
    private static String fetchData(String url) {
        // Долгая HTTP операция
        return "Data from " + url;
    }
}

// ✅ ХОРОШО: Используй обычный Stream для I/O
public class GoodIOExample {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList("url1", "url2", "url3");
        
        urls.stream()
            .map(url -> fetchData(url))
            .forEach(System.out::println);
    }
    
    private static String fetchData(String url) {
        return "Data from " + url;
    }
}

2. Избегай изменяемого состояния

// ❌ ПЛОХО: изменяемое состояние в ParallelStream
public class BadMutableStateExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> results = new ArrayList<>();
        
        // Опасно! Race condition
        numbers.parallelStream()
            .map(n -> n * 2)
            .forEach(results::add); // Не потокобезопасно!
        
        System.out.println("Results: " + results);
    }
}

// ✅ ХОРОШО: используй collect
public class GoodMutableStateExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        List<Integer> results = numbers.parallelStream()
            .map(n -> n * 2)
            .collect(Collectors.toList()); // Потокобезопасно
        
        System.out.println("Results: " + results);
    }
}

Правило выбора

  1. По умолчанию используй Stream — проще и часто быстрее
  2. Переходи на ParallelStream только если:
    • Коллекция > 10000 элементов
    • Операции CPU-интенсивные
    • Нет I/O операций
    • Нет изменяемого состояния
  3. Всегда измеряй производительность с реальными данными

Правильный выбор между Stream и ParallelStream критичен для оптимизации производительности Java приложений.