|
| 1 | +How Java Virtual Threads (from Project Loom, standardized in Java 21+) |
| 2 | +revolutionize the threading model by decoupling Java threads from OS threads. |
| 3 | + |
| 4 | +This is one of the biggest structural evolutions since the JVM was created. |
| 5 | + |
| 6 | +⸻ |
| 7 | + |
| 8 | +1. Quick Idea Before the Diagram |
| 9 | + |
| 10 | +Traditional threads (what your C pthreads and Thread objects use) are 1:1 mapped: |
| 11 | +one Java thread → one OS thread (pthread_create()) |
| 12 | + |
| 13 | +Virtual threads introduce an M:N model: many Java virtual threads → multiplexed over fewer carrier (OS) threads |
| 14 | + |
| 15 | +The JVM acts like a mini operating system, scheduling virtual threads in user space — |
| 16 | +no kernel involvement during blocking operations. |
| 17 | + |
| 18 | +That means you can have millions of concurrent tasks without melting your system. |
| 19 | + |
| 20 | +⸻ |
| 21 | + |
| 22 | +2. ASCII Diagram — Virtual Threads in the JVM (Project Loom) |
| 23 | + |
| 24 | + ┌────────────────────────────────────────────┐ |
| 25 | + │ Your Java Application │ |
| 26 | + │--------------------------------------------│ |
| 27 | + │ Creates many Virtual Threads │ |
| 28 | + │ e.g., 1 million concurrent tasks │ |
| 29 | + └────────────────────────────────────────────┘ |
| 30 | + │ |
| 31 | + ▼ |
| 32 | + ┌───────────────────────────────────────────────────────────┐ |
| 33 | + │ JVM Scheduler (User-Mode) │ |
| 34 | + │-----------------------------------------------------------│ |
| 35 | + │ Manages VThreads (lightweight fibers) │ |
| 36 | + │ Handles blocking, resuming, parking threads internally │ |
| 37 | + │ M:N scheduling — maps many virtual threads onto few OS │ |
| 38 | + │ carrier threads │ |
| 39 | + └──────────────┬──────────────┬──────────────┬──────────────┘ |
| 40 | + │ │ │ |
| 41 | + ▼ ▼ ▼ |
| 42 | + ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ |
| 43 | + │ Carrier Thread │ │ Carrier Thread │ │ Carrier Thread │ |
| 44 | + │ (Real OS │ │ (pthread) │ │ (pthread) │ |
| 45 | + │ Thread) │ │ │ │ │ |
| 46 | + └────────────────┘ └────────────────┘ └────────────────┘ |
| 47 | + │ │ │ |
| 48 | + ▼ ▼ ▼ |
| 49 | + ┌──────────────────────────────────────────────────────────┐ |
| 50 | + │ Operating System (Kernel Layer) │ |
| 51 | + │----------------------------------------------------------│ |
| 52 | + │ CPU Scheduler, Context Switching, Syscalls, etc. │ |
| 53 | + │ Only sees carrier threads, not virtual threads │ |
| 54 | + └──────────────────────────────────────────────────────────┘ |
| 55 | + |
| 56 | +⸻ |
| 57 | + |
| 58 | +3. What’s Happening Here |
| 59 | + |
| 60 | +Step 1: Your code creates virtual threads |
| 61 | + |
| 62 | +Thread.startVirtualThread(() -> { |
| 63 | + // some blocking IO or computation |
| 64 | +}); |
| 65 | + |
| 66 | +Each of these is a virtual thread object managed by the JVM, not the OS. |
| 67 | +Creating one takes microseconds and bytes, compared to milliseconds and megabytes for a native thread. |
| 68 | + |
| 69 | +⸻ |
| 70 | + |
| 71 | +Step 2: The JVM runs a small pool of “carrier” OS threads |
| 72 | +• These are true native threads (pthreads on Linux/macOS). |
| 73 | +• They act as execution engines for many virtual threads. |
| 74 | +• When one virtual thread blocks (say, waiting for IO), the JVM parks it and lets another one run — no kernel-level blocking. |
| 75 | + |
| 76 | +This means the JVM is now its own cooperative scheduler. |
| 77 | + |
| 78 | +⸻ |
| 79 | + |
| 80 | +Step 3: The OS still only sees a few threads |
| 81 | + |
| 82 | +Even if your app spawns millions of virtual threads, the OS might only see 8 carrier threads, one per CPU core. |
| 83 | +The JVM efficiently multiplexes all those user-level threads across them. |
| 84 | + |
| 85 | +⸻ |
| 86 | + |
| 87 | +4. Why This Is a Big Deal |
| 88 | + |
| 89 | +┌────────────────────────────┬──────────────────────────────────────────────┬────────────────────────────────────────┐ |
| 90 | +│ Feature │ Traditional Threads │ Virtual Threads │ |
| 91 | +├────────────────────────────┼──────────────────────────────────────────────┼────────────────────────────────────────┤ |
| 92 | +│ Mapping │ 1:1 │ M:N │ |
| 93 | +│ Creation cost │ High (~1MB stack each) │ Very low (heap-allocated stack, tiny) │ |
| 94 | +│ Blocking IO │ OS-level block │ JVM parks thread, frees carrier │ |
| 95 | +│ Scalability │ Thousands max │ Millions feasible │ |
| 96 | +│ Scheduling │ OS kernel │ JVM (user-mode) │ |
| 97 | +│ Context switch │ Kernel transition │ In-memory, very fast │ |
| 98 | +└────────────────────────────┴──────────────────────────────────────────────┴────────────────────────────────────────┘ |
| 99 | + |
| 100 | +The JVM now acts like a lightweight operating system on top of your real OS — |
| 101 | +it does what pthread does, but for Java, with much less cost. |
| 102 | + |
| 103 | +⸻ |
| 104 | + |
| 105 | +5. Visualization of Multiplexing in Motion |
| 106 | + |
| 107 | +Virtual Threads waiting to run: |
| 108 | +┌──────────────────────────────────────────────┐ |
| 109 | +│ VThread#1 │ VThread#2 │ VThread#3 │ ... │ |
| 110 | +└──────────────────────────────────────────────┘ |
| 111 | + |
| 112 | + ↓ (Scheduled by JVM user-mode scheduler) |
| 113 | + |
| 114 | +Carrier Threads executing (mapped to CPUs): |
| 115 | +┌──────────────┬──────────────┬──────────────┐ |
| 116 | +│ Carrier#1 │ Carrier#2 │ Carrier#3 │ |
| 117 | +│ (pthread) │ (pthread) │ (pthread) │ |
| 118 | +└──────────────┴──────────────┴──────────────┘ |
| 119 | + |
| 120 | +When VThread#1 blocks (e.g., network read), it’s parked. |
| 121 | +Carrier#1 picks up VThread#42 immediately — no kernel wait. |
| 122 | + |
| 123 | +This is similar to goroutines in Go or async tasks in Node, but with real Java Thread APIs, not new syntax. |
| 124 | + |
| 125 | +⸻ |
| 126 | + |
| 127 | +6. How to Use It in Java 21+ |
| 128 | + |
| 129 | +Example code: |
| 130 | + |
| 131 | +public class VirtualThreadDemo { |
| 132 | + public static void main(String[] args) throws InterruptedException { |
| 133 | + try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) { |
| 134 | + for (int i = 0; i < 1_000_000; i++) { |
| 135 | + executor.submit(() -> { |
| 136 | + Thread.sleep(10); |
| 137 | + return Thread.currentThread().getName(); |
| 138 | + }); |
| 139 | + } |
| 140 | + } // Auto-shutdown after all tasks finish |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +That code launches a million concurrent tasks, each as a true Java thread — |
| 145 | +but the JVM runs them using just a handful of carrier pthreads. |
| 146 | + |
| 147 | +⸻ |
| 148 | + |
| 149 | +7. Recap Summary |
| 150 | + |
| 151 | +┌────────────────────────────┬──────────────────────────────────────────────┬────────────────────────────────────────┐ |
| 152 | +│ Layer │ Classic Threads │ Virtual Threads │ |
| 153 | +├────────────────────────────┼──────────────────────────────────────────────┼────────────────────────────────────────┤ |
| 154 | +│ Thread model │ 1 Java = 1 OS thread │ Many Java = few OS threads │ |
| 155 | +│ Scheduler │ OS kernel │ JVM user-mode scheduler │ |
| 156 | +│ Blocking │ Expensive │ Cheap (JVM parks) │ |
| 157 | +│ Creation │ Heavy │ Lightweight │ |
| 158 | +│ Best for │ CPU-bound tasks │ IO-bound, massively concurrent apps │ |
| 159 | +└────────────────────────────┴──────────────────────────────────────────────┴────────────────────────────────────────┘ |
| 160 | + |
| 161 | +⸻ |
| 162 | + |
| 163 | +Virtual Threads essentially make Java’s concurrency model as scalable as Go but |
| 164 | +as simple as C’s pthreads, combining both worlds — the low-level precision of native threads and |
| 165 | +the high-level safety and efficiency of managed scheduling. |
0 commit comments