0Pricing
Java Academy · Lección

Streams paralelos: rendimiento y dificultades

Active streams paralelos, comprenda el conjunto común de hilos y evite errores causados por estado mutable compartido.

Streams paralelos: rendimiento y dificultades es una lección gratuita de Java Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Java Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Java Academy incluye 4 lecciones en total.

Activar los streams paralelos

Llame a .parallel() en cualquier stream o use Collection.parallelStream(). Las operaciones del stream se ejecutan en el ForkJoinPool común (valor predeterminado: núcleos de CPU - 1 hilos).

long count = list.parallelStream()
    .filter(n -> n % 2 == 0)
    .count();
System.out.println(count);

Cuándo resultan útiles los streams paralelos

Los streams paralelos merecen la pena cuando: el conjunto de datos es grande (100 000 elementos o más), cada operación sobre un elemento es costosa desde el punto de vista computacional y el pipeline no tiene estado ni depende del orden.

// Good candidate: CPU-heavy computation on large dataset
long sum = LongStream.rangeClosed(1, 10_000_000)
    .parallel()
    .filter(n -> isPrime(n))
    .sum();
System.out.println(sum);

Cuándo NO usar streams paralelos

Evite los streams paralelos en estos casos: colecciones pequeñas, operaciones limitadas por E/S (bloquean los hilos del ForkJoinPool), operaciones con estado (como ordenar o eliminar duplicados) o cuando el orden sea importante y resulte costoso restaurarlo.

// Bad: I/O bound — blocking ForkJoinPool threads starves other tasks
List<String> result = urls.parallelStream()
    .map(url -> httpGet(url)) // BLOCKS the common pool
    .collect(Collectors.toList());

Error por estado mutable compartido

Los streams paralelos ejecutan operaciones simultáneamente en varios hilos. Modificar una colección mutable compartida provoca condiciones de carrera y resultados incorrectos.

// RACE CONDITION — never do this:
List<Integer> results = new ArrayList<>(); // not thread-safe
numbers.parallelStream().forEach(n -> results.add(n)); // corrupts list!
// Fix:
List<Integer> safe = numbers.parallelStream().collect(Collectors.toList());

Operaciones con estado: sorted y distinct

sorted() y distinct() necesitan ver todos los elementos antes de producir resultados, lo que limita el paralelismo y a menudo hace que la ejecución paralela sea más lenta que la secuencial.

// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());

Operaciones sensibles al orden

findFirst() y forEachOrdered() mantienen el orden de encuentro en los streams paralelos, lo que añade un coste de sincronización. Use findAny() o forEach() cuando el orden no importe.

// Faster in parallel (order-insensitive):
Optional<Integer> any = list.parallelStream().filter(n -> n > 10).findAny();
// Slower in parallel (must preserve order):
Optional<Integer> first = list.parallelStream().filter(n -> n > 10).findFirst();

Elegir el tamaño del pool de hilos

El ForkJoinPool común usa Runtime.getRuntime().availableProcessors() - 1 hilos. Ejecute streams paralelos en un pool personalizado enviándolos dentro de una llamada a ForkJoinPool.invoke().

ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
    () -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();

Comparar el rendimiento: paralelo frente a secuencial

Realice siempre las mediciones con JMH y tamaños de datos realistas. La sobrecarga del paralelismo (división, coordinación de hilos y combinación) solo se justifica cuando el tiempo de cálculo supera ampliamente dicha sobrecarga.

@Benchmark
public long sequential() { return LongStream.rangeClosed(1,1_000_000).sum(); }
@Benchmark
public long parallel()   { return LongStream.rangeClosed(1,1_000_000).parallel().sum(); }

Operaciones de reducción con streams paralelos

reduce() y collect() están diseñadas para funcionar correctamente en paralelo cuando las operaciones son asociativas y el valor identidad es correcto.

// Associative reduce — safe in parallel:
int sum = list.parallelStream().reduce(0, Integer::sum);
// Non-associative: subtraction — NOT safe in parallel:
int bad = list.parallelStream().reduce(0, (a, b) -> a - b); // wrong result!

La capacidad de división es importante

Los streams paralelos dividen la fuente de datos mediante Spliterator. ArrayList y los arrays se dividen en O(1); LinkedList y HashSet se dividen mal, lo que reduce la eficiencia del paralelismo.

Resumen: lista de comprobación de streams paralelos

Antes de usar el paralelismo, compruebe lo siguiente: (1) conjunto de datos grande, (2) operaciones limitadas por CPU, (3) ausencia de estado mutable compartido, (4) independencia del orden y (5) fuente divisible (array o ArrayList). En caso de duda, mida el rendimiento.

Comprobación rápida

¿Qué ocurre cuando añade elementos a una colección que no es segura para hilos dentro de un forEach de un stream paralelo?

Repaso

Los streams paralelos usan ForkJoinPool. Son eficaces para pipelines grandes, limitados por CPU, independientes del orden y sin estado. Nunca modifique el estado compartido. Mida el rendimiento antes de decidirse: el paralelismo suele ser más lento con conjuntos de datos pequeños.

Preguntas frecuentes

¿La lección «Streams paralelos: rendimiento y dificultades» es gratis?

Sí — el texto completo de «Streams paralelos: rendimiento y dificultades» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Java Academy, actualiza a CoddyKit PRO. El curso de Java Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Streams paralelos: rendimiento y dificultades»?

Active streams paralelos, comprenda el conjunto común de hilos y evite errores causados por estado mutable compartido. Practicas Java Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Java Academy?

No se requiere experiencia previa. Java Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Streams paralelos: rendimiento y dificultades»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Java Academy?

Sí. Cada lección de Java Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. flatMap para colecciones anidadas
  2. Streams paralelos: rendimiento y dificultades
  3. Spliterator: división para el paralelismo
  4. Streams infinitos con iterate y generate
← Volver a Java Academy