Here is a complete English summary for your Tistory blog post about Java Parallel Streams, including a refined Fork/Join example, best practices, and notes on CompletableFuture.
Java Parallel Stream and Fork/Join Framework
Parallel programming in Java allows developers to leverage multi-core CPUs efficiently. In this post, we explore how Java handles parallel processing, from basic multithreading to the Fork/Join framework and parallel streams. We'll also touch on pitfalls and practical guidelines for using these features effectively.
1. Sequential Stream
A simple stream processes elements one by one on a single thread, usually the main thread.
int sum = IntStream.rangeClosed(1, 8)
.map(HeavyJob::heavyTask)
.sum();
This example simulates heavy computation per element, taking about 8 seconds in total.
2. Manual Multithreading
We can divide tasks manually using Thread and Runnable:
class SumWorker implements Runnable {
private final int start, end;
public int result;
public SumWorker(int start, int end) {
this.start = start;
this.end = end;
}
public void run() {
for (int i = start; i <= end; i++) {
result += HeavyJob.heavyTask(i);
}
}
}
Manually splitting workload and joining threads reduces execution time but introduces complexity.
3. Thread Pool with ExecutorService
Use ExecutorService and Callable for easier thread management and result aggregation:
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(() -> computeSum(1, 4));
Future<Integer> f2 = pool.submit(() -> computeSum(5, 8));
int total = f1.get() + f2.get();
This simplifies threading logic and scales better.
4. Fork/Join Pattern and RecursiveTask
The Fork/Join pattern splits a task recursively and merges results.
Example
public class ParallelSumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 4;
private final List<Integer> numbers;
public ParallelSumTask(List<Integer> numbers) {
this.numbers = numbers;
}
@Override
protected Integer compute() {
if (numbers.size() <= THRESHOLD) {
MyLogger.log("Direct: " + numbers);
return numbers.stream().mapToInt(HeavyJob::heavyTask).sum();
}
int mid = numbers.size() / 2;
ParallelSumTask left = new ParallelSumTask(numbers.subList(0, mid));
ParallelSumTask right = new ParallelSumTask(numbers.subList(mid, numbers.size()));
left.fork();
int rightResult = right.compute();
int leftResult = left.join();
return leftResult + rightResult;
}
}
This recursive approach simplifies parallel processing and load distribution.
5. ForkJoinPool and Common Pool
You can execute tasks with a custom or common pool:
ForkJoinPool pool = new ForkJoinPool(); // custom pool
int result = pool.invoke(new ParallelSumTask(data));
Or just use:
int result = new ParallelSumTask(data).invoke(); // uses common pool
⚠️ If no pool is explicitly provided, Java uses the common pool by default.
6. Java Parallel Stream
With .parallel(), Java streams use the Fork/Join common pool behind the scenes.
int sum = IntStream.rangeClosed(1, 8)
.parallel()
.map(HeavyJob::heavyTask)
.sum();
This approach requires no manual thread handling. However, because it uses the shared pool, resource contention can become a serious problem in high-concurrency environments.
7. When (Not) to Use Parallel Stream
✅ Good for:
- CPU-bound, compute-heavy operations
- Short-lived, isolated computations
❌ Avoid for:
- I/O-bound operations (e.g., API calls, DB access)
- Web servers handling multiple parallel requests
Why? ForkJoin common pool has a limited number of threads (usually CPU count - 1). If threads are blocked by I/O, they can’t help other tasks.
8. CompletableFuture and Fork/Join Pool
When using CompletableFuture.runAsync() or supplyAsync(), if you don't provide a custom executor, it will also use the Fork/Join common pool by default:
// Uses common pool
CompletableFuture.runAsync(() -> MyLogger.log("common pool"));
// Uses custom pool
ExecutorService customPool = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> MyLogger.log("custom"), customPool);
To avoid blocking the shared pool, always use a custom pool for I/O-bound tasks with CompletableFuture.
Summary
Feature | Suitable For | Custom Thread Pool | Returns Result | Notes |
Stream | Simple, linear logic | ❌ | ✅ | Easy to use |
Parallel Stream | CPU-bound tasks | ❌ (common pool) | ✅ | Risk of contention |
Thread | Fine control needed | ✅ (manual) | ✅ | High complexity |
ExecutorService | Scalable tasks | ✅ | ✅ (Future) | Preferred for most |
ForkJoinTask | Recursive processing | ✅ / common | ✅ | Best for divide-and-conquer |
CompletableFuture | Async pipelines | ✅ recommended | ✅ | Use custom pool! |
Let me know if you want this in Markdown or HTML format, or if you'd like to add a diagram for the Fork/Join flow.
'JAVA' 카테고리의 다른 글
Exception (0) | 2025.04.16 |
---|---|
Default Methods in Java (0) | 2025.04.04 |
Java Optional (0) | 2025.04.04 |
Java Method Reference (0) | 2025.04.02 |
Java Functional Interfaces (0) | 2025.04.01 |