Appearance
Lesson 04 · Virtual Threads
Objectives
After this lesson you will be able to:
- Tell platform threads from virtual threads (Java 21, JEP 444).
- Create virtual threads and a virtual-thread-per-task executor.
- Know when virtual threads help and their key caveats.
Platform vs virtual threads
A platform thread is a thin wrapper over an OS thread — expensive, limited to a few thousand. A virtual thread is a lightweight, JVM-scheduled thread mounted onto a small pool of carrier (platform) threads; you can have millions. Virtual threads became a final feature in Java 21.
java
Thread vt = Thread.ofVirtual().start(() -> work()); // a virtual thread
Thread pt = Thread.ofPlatform().start(() -> work()); // a platform thread
Thread.startVirtualThread(() -> work()); // shortcutWhen a virtual thread blocks (I/O, sleep, lock), the JVM unmounts it from its carrier so the carrier can run another virtual thread — cheap blocking is the whole point.
Virtual-thread-per-task executor
The idiomatic use is one virtual thread per task — no pool sizing needed:
java
try (var ex = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<Integer>> fs = ex.submit(tasks); // thousands of blocking tasks, cheaply
} // close() waits for all tasks (ExecutorService is AutoCloseable since 19)Exam trap
Virtual threads are best for blocking, I/O-bound workloads, not CPU-bound work (they don't add cores). Don't pool them (newFixedThreadPool of virtuals defeats the purpose) — create one per task. They are always daemon and can't be made non-daemon, and their priority is ignored.
Beyond the exam
Structured concurrency (StructuredTaskScope, a preview in 21) treats a group of subtasks as a unit — fork several, join them, and propagate errors/cancellation together. It pairs naturally with virtual threads. The 1Z0-830 expects awareness of virtual threads and the per-task executor more than the preview API.
Key Takeaways
- Virtual threads (Java 21) are lightweight, JVM-scheduled, and mounted on carrier platform threads; create millions cheaply.
- A blocking virtual thread unmounts its carrier — ideal for I/O-bound work, not CPU-bound.
- Use
Executors.newVirtualThreadPerTaskExecutor()(one thread per task); don't pool virtual threads. - Create with
Thread.ofVirtual()/Thread.startVirtualThread(...); they are always daemon.
Lesson Quiz
What is a virtual thread mounted onto when it runs?
Virtual threads are best suited for...
How should you run many tasks on virtual threads?
Which Java version made virtual threads a final (non-preview) feature?
Next: Module 07 Mini-Exam. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.