GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Java Spliterator - parallel processing with Examples Java Spliterator in ParallelLast modified: May 8, 2025
The
A
These characteristics are essential for parallel computation, allowing the Java
runtime to make intelligent decisions about task partitioning and resource
allocation. By leveraging
Java's Fork/Join framework relies on Basic Spliterator Traversal (Sequential)
This example demonstrates basic sequential traversal using a
This example showcases the fundamental iteration capability of a
Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("John", "Jane", "Mike", "Sarah", "Tom");
Spliterator<String> spliterator = names.spliterator();
System.out.println("Characteristics: " + spliterator.characteristics());
System.out.println("Estimated size: " + spliterator.estimateSize());
System.out.println("Elements (sequentially):");
boolean hasNextElement;
do {
hasNextElement = spliterator.tryAdvance(name -> {
System.out.println("Processing: " + name + " by " + Thread.currentThread().getName());
// Simulate some work
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} while (hasNextElement); // Continue until no more elements remain
System.out.println("Finished processing.");
}
}
The output shows the characteristics and initial estimated size. Each element is processed by the main thread. This forms the basis for understanding how Spliterators work before diving into parallel processing. Understanding trySplit
The
It's crucial to understand that calling Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Spliterator<Integer> spliterator1 = numbers.spliterator();
System.out.println("Original spliterator size: " + spliterator1.estimateSize());
// Attempt to split the spliterator
Spliterator<Integer> spliterator2 = spliterator1.trySplit();
if (spliterator2 != null) {
System.out.println("First split size: " + spliterator1.estimateSize());
System.out.println("Second split size: " + spliterator2.estimateSize());
System.out.println("\nProcessing first split (sequentially):");
spliterator1.forEachRemaining(num -> System.out.println(num + " by " + Thread.currentThread().getName()));
System.out.println("\nProcessing second split (sequentially):");
spliterator2.forEachRemaining(num -> System.out.println(num + " by " + Thread.currentThread().getName()));
} else {
System.out.println("Could not split the spliterator. Processing all elements:");
spliterator1.forEachRemaining(System.out::println);
}
}
}
The output demonstrates that the original list is divided. However, both parts
are processed sequentially by the main thread. This example clarifies that
Parallel Execution with ExecutorService and Spliterator
This example shows how to achieve true parallel processing using Spliterator
splits with an
We create a fixed-size thread pool. Each Spliterator (the original and the one
returned by Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Spliterator<Integer> spliterator1 = numbers.spliterator();
Spliterator<Integer> spliterator2 = spliterator1.trySplit(); // s1 is now roughly the second half
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
System.out.println("Submitting tasks for parallel processing...");
// Task for the first split (which is now spliterator2)
if (spliterator2 != null) {
executor.submit(() -> {
System.out.println("Processing second half (split part) by " + Thread.currentThread().getName());
spliterator2.forEachRemaining(num -> {
System.out.println("S2: " + num + " by " + Thread.currentThread().getName());
try { Thread.sleep(100); } catch (InterruptedException e) { /*ignore*/ }
});
});
}
// Task for the remaining part of the original spliterator (spliterator1)
executor.submit(() -> {
System.out.println("Processing first half (original part) by " + Thread.currentThread().getName());
spliterator1.forEachRemaining(num -> {
System.out.println("S1: " + num + " by " + Thread.currentThread().getName());
try { Thread.sleep(100); } catch (InterruptedException e) { /*ignore*/ }
});
});
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
System.out.println("All tasks completed.");
}
}
}
The output will show elements being processed by different threads from the pool (e.g., "pool-1-thread-1", "pool-1-thread-2"). This confirms that the processing of the two data partitions occurs in parallel. This manual approach gives fine-grained control over parallel execution. Leveraging Parallel Streams with Spliterator
Java Streams provide a high-level API for processing sequences of elements.
Parallel streams use Spliterators internally to divide work among multiple
threads. This example shows two ways to get parallel streams:
The Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("apple", "banana", "cherry", "date",
"elderberry", "fig", "grape", "honeydew");
System.out.println("Processing with collection.parallelStream():");
words.parallelStream().forEach(word ->
System.out.println(word + " processed by " + Thread.currentThread().getName())
);
System.out.println("\nProcessing with StreamSupport.stream(spliterator, true):");
Spliterator<String> spliterator = words.spliterator();
Stream<String> parallelStreamFromSpliterator = StreamSupport.stream(spliterator, true);
parallelStreamFromSpliterator.forEach(word ->
System.out.println(word + " processed by " + Thread.currentThread().getName())
);
System.out.println("Finished processing with parallel streams.");
}
}
The output reveals how multiple threads execute tasks concurrently, efficiently
distributing workload across available processor cores. By leveraging Java's
Fork/Join framework, the Streams API seamlessly handles parallel execution,
ensuring optimal resource utilization without requiring manual thread
management. This example illustrates how Parallel Processing of Primitive Data Types
Java provides specialized Spliterators for primitive types like
We first demonstrate creating a parallel Main.java
package com.zetcode;
import java.util.Arrays;
import java.util.Spliterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
public class Main {
public static void main(String[] args) throws InterruptedException {
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
// Method 1: Using parallel IntStream from Spliterator.OfInt
Spliterator.OfInt spliteratorForStream = Arrays.spliterator(numbers);
IntStream parallelIntStream = StreamSupport.intStream(spliteratorForStream, true);
long sum1 = parallelIntStream.sum();
System.out.println("Sum using parallel IntStream: " + sum1);
// Method 2: Manual parallel processing with ExecutorService
System.out.println("\nManual parallel processing of primitive array:");
Spliterator.OfInt s1 = Arrays.spliterator(numbers);
Spliterator.OfInt s2 = s1.trySplit();
LongAdder partialSum1 = new LongAdder();
LongAdder partialSum2 = new LongAdder();
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
if (s2 != null) {
executor.submit(() -> {
s2.forEachRemaining((int val) -> {
partialSum2.add(val);
});
});
}
executor.submit(() -> {
s1.forEachRemaining((int val) -> {
partialSum1.add(val);
});
});
executor.shutdown();
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
}
long totalSum = partialSum1.sum() + partialSum2.sum();
System.out.println("Sum using manual parallel processing: " + totalSum);
}
}
This example showcases two approaches for parallel processing of primitive
arrays. Using parallel streams is often more concise. Manual control with
Parallel Summation of Numbers using Spliterator
This example demonstrates summing a list of numbers in parallel. We obtain a
This pattern is common for "divide and conquer" parallel algorithms. The work of
summation is divided between threads, and results are aggregated. It highlights
how Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> numbers = IntStream.rangeClosed(1, 10000)
.boxed()
.collect(Collectors.toList());
Spliterator<Integer> spliterator1 = numbers.spliterator();
Spliterator<Integer> spliterator2 = spliterator1.trySplit(); // s1 is now the second half
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
// Task for the first part (which is spliterator2)
Callable<Long> sumTask1 = () -> {
long sum = 0;
if (spliterator2 != null) {
SummingConsumer consumer = new SummingConsumer();
spliterator2.forEachRemaining(consumer);
sum = consumer.getTotal();
}
System.out.println("Sum from task 1 (thread " + Thread.currentThread().getName() + "): " + sum);
return sum;
};
// Task for the second part (remaining of spliterator1)
Callable<Long> sumTask2 = () -> {
SummingConsumer consumer = new SummingConsumer();
spliterator1.forEachRemaining(consumer);
long sum = consumer.getTotal();
System.out.println("Sum from task 2 (thread " + Thread.currentThread().getName() + "): " + sum);
return sum;
};
Future<Long> future1 = executor.submit(sumTask1);
Future<Long> future2 = executor.submit(sumTask2);
long totalSum = future1.get() + future2.get();
System.out.println("Total sum calculated in parallel: " + totalSum);
long expectedSum = numbers.stream().mapToLong(Integer::longValue).sum();
System.out.println("Expected sum (sequential stream): " + expectedSum);
executor.shutdown();
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
}
}
// Helper class for summing, as lambda variable for sum must be effectively final
static class SummingConsumer implements Consumer<Integer> {
private long total = 0;
@Override
public void accept(Integer value) {
total += value;
}
public long getTotal() {
return total;
}
}
}
The splitting mechanism is achieved using
To perform the parallel summation, an
A helper class, Once both tasks complete execution, their results are combined to obtain the total sum in parallel. The computed result is then verified against a sequential sum obtained through a standard stream operation. This ensures correctness and provides insight into the performance benefits of parallel execution. Parallel Data Transformation using Spliterator
This example demonstrates parallel processing using This is useful for CPU-bound transformation tasks on large datasets. Splitting the data allows multiple cores to work on the transformation simultaneously, potentially speeding up the overall process significantly. Main.java
package com.zetcode;
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws Exception {
List<String> words = List.of("alpha", "bravo", "charlie", "delta", "echo",
"foxtrot", "golf", "hotel", "india", "juliett");
Spliterator<String> s1 = words.spliterator();
Spliterator<String> s2 = s1.trySplit(); // s1 is now the second half
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
Callable<List<String>> transformTask1 = () -> {
List<String> result = new ArrayList<>();
if (s2 != null) { // s2 is the first half
s2.forEachRemaining(word -> {
result.add(word.toUpperCase() + " (processed by " + Thread.currentThread().getName() + ")");
});
}
return result;
};
Callable<List<String>> transformTask2 = () -> {
List<String> result = new ArrayList<>();
s1.forEachRemaining(word -> { // s1 is the second half
result.add(word.toUpperCase() + " (processed by " + Thread.currentThread().getName() + ")");
});
return result;
};
Future<List<String>> future1 = executor.submit(transformTask1);
Future<List<String>> future2 = executor.submit(transformTask2);
List<String> combinedResult = new ArrayList<>();
combinedResult.addAll(future1.get());
combinedResult.addAll(future2.get());
System.out.println("Transformed words in parallel:");
combinedResult.forEach(System.out::println);
executor.shutdown();
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
}
System.out.println("\nSequentially transformed for order reference:");
words.stream().map(String::toUpperCase).forEach(System.out::println);
}
}
The splitting mechanism divides the original
To facilitate true parallel execution, a fixed thread pool
(
Each task uses It is important to note that order consistency is not guaranteed unless explicitly managed. Since the processing occurs across multiple threads, the final merged result may differ from the original sequence. For reference, a sequential transformation using a standard stream is displayed alongside the parallel output.
Finally, the Parallel Data Aggregation (e.g., Counting) using Spliterator
This example demonstrates parallel data aggregation, specifically counting elements
that match a certain criterion. We use a list of strings (simulating lines from a
file). The goal is to count how many strings contain a specific keyword.
The This approach is effective for operations like filtering and counting on large datasets. By dividing the dataset and processing parts concurrently, we can often achieve better performance than a purely sequential approach. Main.java
package com.zetcode;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws Exception {
List<String> lines = List.of(
"The quick brown fox",
"jumps over the lazy dog",
"A B C D E F G",
"Another line with fox here",
"Spliterators are useful for parallel fox processing",
"The lazy fox is quick"
);
String keyword = "fox";
Spliterator<String> s1 = lines.spliterator();
Spliterator<String> s2 = s1.trySplit(); // s1 is now the second half
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
Callable<Integer> countTask1 = () -> {
AtomicInteger count = new AtomicInteger(0);
if (s2 != null) { // s2 is the first half
s2.forEachRemaining(line -> {
if (line.contains(keyword)) {
count.incrementAndGet();
}
});
}
System.out.println("Count from task 1 (thread " + Thread.currentThread().getName() + "): " + count.get());
return count.get();
};
Callable<Integer> countTask2 = () -> {
AtomicInteger count = new AtomicInteger(0);
s1.forEachRemaining(line -> { // s1 is the second half
if (line.contains(keyword)) {
count.incrementAndGet();
}
});
System.out.println("Count from task 2 (thread " + Thread.currentThread().getName() + "): " + count.get());
return count.get();
};
Future<Integer> future1 = executor.submit(countTask1);
Future<Integer> future2 = executor.submit(countTask2);
int totalCount = future1.get() + future2.get();
System.out.println("Total lines containing '" + keyword + "': " + totalCount);
long expectedCount = lines.stream().filter(line -> line.contains(keyword)).count();
System.out.println("Expected count (sequential stream): " + expectedCount);
executor.shutdown();
if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
}
}
}
In this example, a list of strings is processed in parallel to count occurrences
of the word "fox". Each task uses an
A key component in this implementation is the use of
Once both tasks complete execution, their partial results are retrieved via
SourceJava Spliterator Documentation
This tutorial covered the Java AuthorMy name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts. List all Java tutorials. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |