14.5 Parallel Streams and Primitive Streams
1. Parallel Streams
parallelStream() processes stream operations in parallel using multiple CPU cores via the internal ForkJoinPool.
import java.util.*;
import java.util.stream.*;
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000)
.boxed().collect(Collectors.toList());
// Sequential stream
long start1 = System.currentTimeMillis();
long sum1 = numbers.stream()
.mapToLong(Integer::longValue).sum();
System.out.println("Sequential: " + (System.currentTimeMillis() - start1) + "ms, sum=" + sum1);
// Parallel stream
long start2 = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(Integer::longValue).sum();
System.out.println("Parallel: " + (System.currentTimeMillis() - start2) + "ms, sum=" + sum2);
When to Use Parallel Streams
| Suitable | Not suitable |
|---|---|
| Very large data sets (tens of thousands or more) | Small data sets |
| CPU-intensive operations | Simple, fast operations |
| Independent operations (order doesn't matter) | Order-sensitive processing |
| Data structures easy to split, like ArrayList | Data structures hard to split, like LinkedList |
// Suitable for parallel: complex operations, large data
List<Integer> bigList = IntStream.rangeClosed(1, 100_000)
.boxed().collect(Collectors.toList());
long result = bigList.parallelStream()
.filter(n -> n % 2 == 0)
.mapToLong(n -> (long) n * n)
.sum();
// Not suitable for parallel: order-sensitive, I/O-bound
List<String> files = List.of("a.txt", "b.txt", "c.txt");
// File I/O operations may not benefit from parallelism and can break ordering
Warning: Never Share Mutable State
// Dangerous! Modifying shared state
List<Integer> shared = new ArrayList<>();
IntStream.range(0, 1000).parallel().forEach(shared::add); // ConcurrentModificationException!
// Safe: use collect instead
List<Integer> safe = IntStream.range(0, 1000)
.parallel()
.boxed()
.collect(Collectors.toList()); // thread-safe internally
2. Primitive Streams
Generic Stream<Integer> has overhead due to object boxing. IntStream, LongStream, and DoubleStream work directly with primitive types for much better performance.
IntStream
// Creation methods
IntStream s1 = IntStream.of(1, 2, 3, 4, 5);
IntStream s2 = IntStream.range(1, 6); // 1,2,3,4,5 (exclusive end)
IntStream s3 = IntStream.rangeClosed(1, 5); // 1,2,3,4,5 (inclusive end)
IntStream s4 = "Hello".chars(); // int code of each character
// Statistical operations (no boxing overhead)
int sum = IntStream.rangeClosed(1, 100).sum(); // 5050
double avg = IntStream.rangeClosed(1, 100).average().orElse(0); // 50.5
int max = IntStream.of(3, 1, 4, 1, 5, 9).max().orElse(0); // 9
long count = IntStream.rangeClosed(1, 100).count(); // 100
// Summary statistics in one call
IntSummaryStatistics stats = IntStream.rangeClosed(1, 100).summaryStatistics();
System.out.println(stats.getSum()); // 5050
System.out.println(stats.getAverage()); // 50.5
System.out.println(stats.getMax()); // 100
// Convert to array
int[] arr = IntStream.rangeClosed(1, 5).toArray(); // [1, 2, 3, 4, 5]
// Boxing (convert to Stream<Integer>)
Stream<Integer> boxed = IntStream.rangeClosed(1, 5).boxed();
List<Integer> list = boxed.collect(Collectors.toList());
LongStream and DoubleStream
// LongStream: handling large numbers
long factorial = LongStream.rangeClosed(1, 20)
.reduce(1L, (a, b) -> a * b);
System.out.println(factorial); // 2432902008176640000
// DoubleStream: floating-point operations
double[] prices = {100.0, 200.0, 300.0, 150.0};
DoubleSummaryStatistics priceStats = DoubleStream.of(prices).summaryStatistics();
System.out.printf("Average price: %.2f%n", priceStats.getAverage()); // 187.50
mapToInt, mapToLong, mapToDouble — Converting to Primitive Streams
record Product(String name, int price, int quantity) {}
List<Product> products = List.of(
new Product("Apple", 1000, 50),
new Product("Banana", 800, 30),
new Product("Strawberry", 2000, 20)
);
// Total inventory value (no int boxing overhead)
int totalValue = products.stream()
.mapToInt(p -> p.price() * p.quantity())
.sum();
System.out.println("Total inventory value: " + totalValue); // 126000
// Most expensive product
OptionalInt maxPrice = products.stream()
.mapToInt(Product::price)
.max();
System.out.println("Max price: " + maxPrice.orElse(0)); // 2000
// Average price
OptionalDouble avgPrice = products.stream()
.mapToDouble(Product::price)
.average();
System.out.printf("Average price: %.0f%n", avgPrice.orElse(0)); // 1267
3. Advanced Stream Creation Methods
// Stream.of
Stream<String> s1 = Stream.of("A", "B", "C");
// Stream.generate - infinite stream (always use limit!)
Stream<Double> randoms = Stream.generate(Math::random).limit(5);
Stream<String> helloStream = Stream.generate(() -> "hello").limit(3);
// Stream.iterate - generate from initial value + function
Stream<Integer> evens = Stream.iterate(0, n -> n + 2).limit(10);
// 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
// Stream.iterate with condition (Java 9+)
Stream<Integer> under100 = Stream.iterate(1, n -> n < 100, n -> n * 2);
// 1, 2, 4, 8, 16, 32, 64
// Stream.concat - merge two streams
Stream<String> combined = Stream.concat(
Stream.of("A", "B"),
Stream.of("C", "D")
);
combined.forEach(System.out::print); // ABCD
// Stream.builder
Stream.Builder<String> builder = Stream.builder();
builder.add("A");
builder.add("B");
if (true) builder.add("C");
Stream<String> built = builder.build();
Practical guide to parallel streams:
-
Measure first: Parallel streams are not always faster. For small data or simple operations, they can actually be slower. Measure with
System.nanoTime()before adopting. -
ForkJoinPool thread count: By default, it uses
Runtime.getRuntime().availableProcessors() - 1threads. Indiscriminate use ofparallelStream()in a Spring web server can exhaust the shared thread pool. -
Prefer primitive streams: Use
IntStream,LongStream, etc. instead ofStream<Integer>,Stream<Long>to eliminate boxing overhead. The difference is especially significant for numeric aggregations.
// Slow: boxing overhead
long sum1 = Stream.iterate(1, n -> n + 1).limit(1_000_000)
.mapToLong(Integer::longValue).sum();
// Fast: use primitive stream from the start
long sum2 = LongStream.rangeClosed(1, 1_000_000).sum();