Processes, Threads, Coroutines, and Virtual Threads: A Deep Dive into Java Concurrency
This article explains the differences and relationships between processes, threads, coroutines, and virtual threads, provides detailed Java code examples, compares their characteristics and performance, and offers practical guidance on choosing the right concurrency model for various scenarios.
Preface
Virtual threads have become popular recently, greatly improving system performance, but many developers still confuse processes, threads, coroutines, and virtual threads. This article clarifies these concepts.
1. Processes and Threads
Processes are independent execution environments with their own address space, stack, and resources, similar to separate workshops in a factory. Threads are workers within a process that share the process's resources.
Process characteristics : independent address space, safety (a crash does not affect others), high creation overhead, complex inter‑process communication.
Thread characteristics : lightweight, share memory, simple communication, require synchronization.
// Java example of creating a process
public class ProcessExample {
public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("calc.exe");
Process p = pb.start();
System.out.println("Process ID: " + p.pid());
System.out.println("Alive: " + p.isAlive());
int exitCode = p.waitFor();
System.out.println("Exit code: " + exitCode);
}
}Creating Threads
// Three ways to create a thread in Java
public class ThreadExample {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.start();
Thread t2 = new Thread(new MyRunnable());
t2.start();
Thread t3 = new Thread(() -> {
System.out.println("Lambda thread running: " + Thread.currentThread().getName());
});
t3.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread running: " + Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable running: " + Thread.currentThread().getName());
}
}2. Deep Dive into Threads
Modern operating systems use three thread models:
1. User‑Level Threads (ULT)
Implemented entirely in user space; the OS is unaware of them. Creation, scheduling, and synchronization are handled by a user‑level library.
Advantages: no kernel mode switch, low overhead, customizable scheduling.
Disadvantages: a blocked thread blocks the whole process, cannot exploit multiple CPUs.
2. Kernel‑Level Threads (KLT)
Managed directly by the OS kernel; each kernel thread maps to a scheduling entity.
Advantages: a blocked thread does not block others, can run on multiple cores.
Disadvantages: higher context‑switch cost, requires system calls.
3. Hybrid Model
Most modern OSes use a hybrid model that maps user‑level threads onto kernel threads. Java threads follow this model.
3. Coroutines
Coroutines are even lighter than threads and are scheduled by the programmer in user space.
Extremely lightweight – millions can be created.
Cooperative scheduling – explicit yield points.
Low‑cost switching – no kernel involvement.
Write asynchronous code in a synchronous style.
// Pseudo‑code illustrating coroutine concepts in Java (requires a library like Quasar)
public class CoroutineExample {
public static void main(String[] args) {
Coroutine c1 = new Coroutine(() -> {
System.out.println("Coroutine 1 start");
Coroutine.yield();
System.out.println("Coroutine 1 resume");
});
Coroutine c2 = new Coroutine(() -> {
System.out.println("Coroutine 2 start");
Coroutine.yield();
System.out.println("Coroutine 2 resume");
});
c1.run();
c2.run();
c1.run();
c2.run();
}
}4. Virtual Threads
Java 19 introduced virtual threads, a major breakthrough in the Java concurrency model. They address the limitations of platform threads.
Why Virtual Threads?
Traditional threads suffer from limited thread count, large memory overhead (default 1 MB stack), and expensive context switches.
Implementation Principle
Virtual threads are lightweight Java‑level threads scheduled onto a small pool of platform threads. When a virtual thread blocks, the JVM detaches it from the platform thread, allowing the platform thread to run other virtual threads.
// Simple example of creating a virtual thread
public class VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
Thread vt = Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread running: " + Thread.currentThread());
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
});
vt.join();
}
}Advantages
Lightweight – millions can be created.
Low‑cost blocking – blocking does not block the platform thread.
Simplified concurrency – write blocking code in a synchronous style.
Compatibility – virtual threads implement Thread, so existing APIs work.
5. Choosing the Right Model
CPU‑Intensive Tasks
Use a fixed‑size platform thread pool equal to the number of CPU cores.
// CPU‑intensive task example
public class CpuIntensiveTask {
public static void main(String[] args) throws InterruptedException {
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(processors);
for (int i = 0; i < 100; i++) {
executor.submit(() -> compute());
}
executor.shutdown();
}
private static void compute() {
long result = 0;
for (long i = 0; i < 100_000_000L; i++) {
result += i * i;
}
System.out.println("Result: " + result);
}
}IO‑Intensive Tasks
Virtual threads shine here because they do not occupy a platform thread while waiting for IO.
// IO‑intensive task using virtual threads
public class IoIntensiveTask {
public static void main(String[] args) throws InterruptedException {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
String data = httpGet("https://api.example.com/data");
processData(data);
});
}
}
}
private static String httpGet(String url) {
try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); }
return "response data";
}
private static void processData(String data) {
System.out.println("Processing: " + data);
}
}Mixed Workloads
Combine virtual threads for IO and a fixed thread pool for CPU work.
// Mixed workload example
public class MixedTask {
public static void main(String[] args) throws InterruptedException {
try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
futures.add(ioExecutor.submit(() -> fetchData()));
}
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService cpuExecutor = Executors.newFixedThreadPool(processors);
for (Future<String> f : futures) {
cpuExecutor.submit(() -> {
try {
String data = f.get();
processDataIntensively(data);
} catch (Exception e) { e.printStackTrace(); }
});
}
cpuExecutor.shutdown();
}
}
private static String fetchData() { return "data"; }
private static void processDataIntensively(String d) { /* heavy CPU work */ }
}6. Performance Comparison
A benchmark shows that handling 10 000 IO‑bound tasks with a 200‑thread pool takes about 50 seconds, while using virtual threads completes in roughly 1 second—a 50× speedup.
// Benchmark comparing platform threads and virtual threads
public class PerformanceComparison {
private static final int TASK_COUNT = 10_000;
private static final int IO_DELAY_MS = 100;
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
testPlatformThreads();
long platform = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
testVirtualThreads();
long virtual = System.currentTimeMillis() - start;
System.out.println("Platform time: " + platform + "ms");
System.out.println("Virtual time: " + virtual + "ms");
System.out.println("Speedup: " + ((double) platform / virtual) + "x");
}
private static void testPlatformThreads() throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(200);
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
exec.submit(() -> {
try { Thread.sleep(IO_DELAY_MS); } catch (InterruptedException e) { e.printStackTrace(); }
finally { latch.countDown(); }
});
}
latch.await();
exec.shutdown();
}
private static void testVirtualThreads() throws InterruptedException {
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
exec.submit(() -> {
try { Thread.sleep(IO_DELAY_MS); } catch (InterruptedException e) { e.printStackTrace(); }
finally { latch.countDown(); }
});
}
latch.await();
}
}
}7. Summary
Choosing the appropriate concurrency model depends on isolation needs, workload type, and scalability requirements:
Use separate processes for strong isolation (micro‑services).
Use platform threads for CPU‑bound tasks, matching thread count to CPU cores.
Use virtual threads for IO‑bound workloads to achieve massive concurrency with minimal overhead.
Consider coroutines in languages that provide native support (e.g., Go) when extreme concurrency is required.
Remember: there is no universally best concurrency model—only the one that best fits your specific problem.
8. Future Outlook
Virtual threads are a major step forward, but further evolution is expected:
Better tooling for debugging and monitoring virtual threads.
More intelligent scheduling algorithms.
Integration with reactive and actor models.
Hardware‑level optimizations (e.g., DPU cooperation).
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.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
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.
