Target Audience: Beginner → Architect
JDK Context: JDK 25 LTS (latest LTS generation model, including Virtual Threads & Structured Concurrency)
Focus: Every major Java thread/concurrency type with practical code examples, deep explanations, and architectural alternatives.
Modern Java concurrency is no longer limited to Thread and synchronized. As of JDK 25 LTS, Java provides multiple concurrency models:
Each model serves a different architectural purpose.
This guide explains:
Platform threads map 1:1 to OS threads.
Java Thread 1 → OS Thread 1
Java Thread 2 → OS Thread 2
They are expensive compared to virtual threads.
public class PlatformThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread());
});
thread.start();
}
}
Use ExecutorService or Virtual Threads for scalable applications.
Creating threads manually does not scale.
Task → Queue → Thread Pool → Worker Thread
import java.util.concurrent.*;
public class FixedPoolExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() ->
System.out.println("Task " + taskId + " handled by " +
Thread.currentThread().getName())
);
}
executor.shutdown();
}
}
+-------------------+
| Task Queue |
+-------------------+
↓
+-------------------+
| Thread Pool (4) |
| T1 T2 T3 T4 |
+-------------------+
| Scenario | Better Option |
|---|---|
| Blocking I/O | Virtual Threads |
| CPU-intensive | ForkJoinPool |
| Async Pipelines | CompletableFuture |
Used for periodic or delayed tasks.
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Running every 2 seconds");
}, 0, 2, TimeUnit.SECONDS);
Use case:
Alternative: java.time + structured scheduler frameworks.
Designed for divide-and-conquer tasks.
graph TD A[Task] --> B[Subtask 1] A --> C[Subtask 2] B --> D[Worker 1] C --> E[Worker 2] E -->|Steal| D
import java.util.concurrent.*;
class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start, end;
private static final int THRESHOLD = 1000;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
protected Long compute() {
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++)
sum += array[i];
return sum;
}
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
return right.compute() + left.join();
}
}
Alternative: Parallel Streams
Modern non-blocking model.
import java.util.concurrent.*;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(msg -> msg + " World")
.thenAccept(System.out::println)
.join();
}
}
graph LR A[SupplyAsync] --> B[Transform] B --> C[Consume]
Virtual threads for simpler blocking code.
Major innovation from Project Loom.
public class VirtualThreadExample {
public static void main(String[] args) throws Exception {
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
Thread.sleep(100);
System.out.println(Thread.currentThread());
});
}
}
}
}
Virtual Thread
↓
JVM Scheduler
↓
Platform Thread Pool
↓
CPU Core
Traditional:
10,000 requests → 10,000 OS threads → Memory pressure
Virtual threads:
10,000 requests → Few OS threads → Efficient scheduling
Provides lifecycle control.
import java.util.concurrent.*;
public class StructuredExample {
public static void main(String[] args) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var future1 = scope.fork(() -> fetchUser());
var future2 = scope.fork(() -> fetchOrders());
scope.join();
scope.throwIfFailed();
System.out.println(future1.get() + " " + future2.get());
}
}
static String fetchUser() { return "User"; }
static String fetchOrders() { return "Orders"; }
}
graph TD P[Parent Task] P --> C1[Child Task 1] P --> C2[Child Task 2] C1 --> P C2 --> P
Benefits:
List<Integer> numbers = List.of(1,2,3,4,5);
numbers.parallelStream()
.map(n -> n * 2)
.forEach(System.out::println);
Internally uses ForkJoinPool.
Best for:
| Type | Best For | Blocking Friendly | Scales High | Complexity |
|---|---|---|---|---|
| Platform Thread | CPU tasks | No | Limited | Low |
| Fixed Pool | Controlled workloads | No | Medium | Medium |
| Fork/Join | CPU parallelism | No | High | High |
| CompletableFuture | Async pipelines | Partial | High | Medium |
| Virtual Threads | I/O workloads | Yes | Very High | Low |
| Structured Concurrency | Scoped tasks | Yes | High | Medium |
→ ForkJoinPool or Fixed Thread Pool
→ Virtual Threads
→ CompletableFuture
→ Structured Concurrency + Virtual Threads
Concurrency in modern Java is no longer about choosing between Thread and Runnable.
As of JDK 25 LTS:
Architecturally, the correct model depends on:
Mastering these models enables building systems that are scalable, efficient, and maintainable.