ParallelStreamsExamples.java

package fr.univtln.bruno.samples.java101.tp3.functional;

import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Examples demonstrating parallel streams usage and common pitfalls.
 *
 * <p>Shows performance measurement and the dangers of shared mutable state when using
 * parallel streams; recommends reduction operations as the correct pattern.</p>
 */
@Slf4j
public class ParallelStreamsExamples {
  /**
   * Compare sequential vs parallel stream performance for a simple summation.
   * This is illustrative only — real benchmarking requires JMH and controlling for warmup.
   */
  public static void parallelSumExample() {
    log.info("=== Parallel sum example (measure) ===");
    List<Integer> data = IntStream.rangeClosed(1, 1_000_000).boxed().toList();

    long t0 = System.currentTimeMillis();
    long sumSeq = data.stream().mapToLong(Integer::longValue).sum();
    long t1 = System.currentTimeMillis();

    log.debug("Sequential sum took {} ms", (t1 - t0));

    long sumParStart = System.currentTimeMillis();
    long sumPar = data.parallelStream().mapToLong(Integer::longValue).sum();
    long sumParEnd = System.currentTimeMillis();

    log.info("seq sum={} time={}ms", sumSeq, (t1 - t0));
    log.info("par sum={} time={}ms", sumPar, (sumParEnd - sumParStart));
    log.debug("Parallel sum took {} ms", (sumParEnd - sumParStart));
  }

  /**
   * Demonstrates the pitfall of mutating shared state (AtomicInteger used here still risks
   * producing confusing results and should be avoided). Shows the correct pattern using reduction.
   */
  public static void sharedMutablePitfall() {
    log.info("=== Shared mutable state pitfall ===");
    List<Integer> data = IntStream.rangeClosed(1, 1000).boxed().toList();

    AtomicInteger counter = new AtomicInteger(0);
    // WRONG: side-effect in parallel stream leads to race / unpredictable result
    data.parallelStream().forEach(counter::addAndGet);
    log.info("Counter (with parallel forEach) = {} (should be {})", counter.get(), data.stream().mapToInt(Integer::intValue).sum());

    int reduced = data.parallelStream().mapToInt(Integer::intValue).reduce(0, Integer::sum);
    log.info("Reduced (parallel) = {}", reduced);

    // Student note: prefer stateless reduction or collectors. For example, using a thread-safe collector:
    // int sum = data.parallelStream().mapToInt(Integer::intValue).sum(); // built-in is safe
    // Or: data.parallelStream().collect(Collectors.summingInt(Integer::intValue));
  }

  /**
   * Runner for parallel streams examples.
   *
   * @param args ignored
   */
  public static void main(String[] args) {
    parallelSumExample();
    sharedMutablePitfall();
  }
}