Mastering Concurrency and Parallelism in Java: From Basics to Advanced APIs
This article explains the concepts of concurrency, parallelism, and serial execution, describes common multi‑core scheduling algorithms, and demonstrates Java's concurrent programming tools—including Future, Fork/Join, Stream API, and CompletableFuture—through clear code examples and practical guidelines.
1 Concurrency and Parallelism
The operating system creates the illusion of running multiple programs simultaneously by rapidly switching CPU contexts, which users cannot perceive. Concurrency (multiple tasks appear to run at the same time) is a macro‑level phenomenon, while at the micro‑level tasks are interleaved, improving resource utilization and throughput.
Parallelism occurs when multiple CPU cores execute different threads at the same instant. A single core can execute only one thread per time slice (unless hyper‑threading provides logical cores). When N cores run N threads, the execution is parallel. Serial execution runs tasks sequentially on one core.
These relationships are illustrated below:
Further diagrams show serial, concurrent, and parallel execution, as well as their respective execution times and task handling approaches.
Concurrency does not always imply parallelism; concurrency is logical interleaving, while parallelism requires distinct cores executing simultaneously.
You eat, the phone rings, you finish eating before answering – no concurrency or parallelism.
You pause eating to answer the phone, then resume – concurrency.
You eat while talking on the phone – parallelism.
2 Multi‑core Scheduling Algorithms
Common algorithms for multi‑core CPUs aim to maximize core utilization and overall system performance:
Preemptive Scheduling : The OS can interrupt a running task at any time and reassign the processor, possibly migrating tasks across cores.
Fair Scheduling : CPU time is distributed evenly among tasks, balancing load across cores.
Load‑Balancing Scheduling : Tasks are moved from heavily loaded cores to lighter ones to keep all cores busy.
Priority Scheduling : Each task receives a priority; higher‑priority tasks are scheduled first, possibly on specific cores.
Hybrid Scheduling : Combines multiple strategies (e.g., fair + load‑balancing) to suit different workloads.
Preemptive scheduling is the most widely used; it improves responsiveness and real‑time behavior by allowing the OS to interrupt low‑priority tasks for higher‑priority ones.
3 Java Parallel Programming
3.1 Future
Future represents the result of an asynchronous computation. It provides methods such as isDone(), cancel(), get(), and get(timeout, TimeUnit). Example:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureParallelExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<Integer> task1 = () -> { Thread.sleep(2000); return 10; };
Callable<Integer> task2 = () -> { Thread.sleep(3000); return 20; };
Future<Integer> future1 = executor.submit(task1);
Future<Integer> future2 = executor.submit(task2);
System.out.println("Asynchronous computation is executing.");
Integer result1 = future1.get();
System.out.println("Task 1 result: " + result1);
Integer result2 = future2.get();
System.out.println("Task 2 result: " + result2);
executor.shutdown();
}
}The two tasks run concurrently and their results are retrieved with Future.get().
3.2 Fork/Join
The Fork/Join framework splits a large task into smaller subtasks and joins their results. Example computing the Fibonacci sequence:
public class FibonacciRecursion {
public static int fibonacciRecursive(int n) {
if (n <= 1) return n;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
public static void main(String[] args) {
int n = 10;
System.out.println("Fibonacci of " + n + " is " + fibonacciRecursive(n));
}
}Using Fork/Join:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class FibonacciFork extends RecursiveTask<Integer> {
final int n;
public FibonacciFork(int n) { this.n = n; }
@Override
protected Integer compute() {
if (n <= 1) return n;
FibonacciFork f1 = new FibonacciFork(n - 1);
FibonacciFork f2 = new FibonacciFork(n - 2);
f1.fork();
return f2.compute() + f1.join();
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
FibonacciFork fib = new FibonacciFork(10);
Integer result = pool.invoke(fib);
System.out.println(result);
}
}The Fork/Join pool automatically distributes subtasks across available cores.
3.3 Stream API
Java 8 introduced Stream API for functional‑style processing of collections. Streams can be parallelized, leveraging the Fork/Join framework. Example:
public class StreamDemo {
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6,7,8,9)
.reduce((a, b) -> a + b)
.ifPresent(System.out::println); // prints 45
}
}Parallel stream:
public class StreamParallelDemo {
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6,7,8,9)
.parallel()
.reduce((a, b) -> a + b)
.ifPresent(System.out::println);
}
}Parallel streams split the workload across multiple threads, but overhead may outweigh benefits for small data sets.
3.4 CompletableFuture
CompletableFuture extends Future with a fluent API for chaining, combining, and handling exceptions in asynchronous tasks.
Simple async task:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 100);
future.thenAccept(result -> System.out.println("Async result: " + result));
}
}Combining two futures:
CompletableFuture<Integer> combined = future1.thenCombine(future2, (r1, r2) -> r1 + r2);
combined.thenAccept(r -> System.out.println("Sum: " + r));Handling exceptions:
future.exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return null;
});Custom executor example demonstrates running asynchronous work on a dedicated thread pool.
4 Summary
To master Java concurrency, study the fundamentals, become familiar with the rich set of concurrency utilities (Thread, Executor, Future, Fork/Join, Stream, CompletableFuture), practice multithreaded programming, understand concurrency models, learn design patterns such as producer‑consumer, and apply the knowledge in real projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Linux
Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
