Skip to content

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());                // shortcut

When 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

Lesson Quiz · Virtual Threads0 / 4
  1. What is a virtual thread mounted onto when it runs?

    • AAnother virtual thread
    • BA carrier (platform) thread
    • CThe GC thread
    • DA process
  2. Virtual threads are best suited for...

    • ACPU-bound number crunching
    • BBlocking, I/O-bound tasks
    • CGUI rendering
    • DReal-time audio
  3. How should you run many tasks on virtual threads?

    • AExecutors.newFixedThreadPool with virtual threads
    • BExecutors.newVirtualThreadPerTaskExecutor()
    • CA single virtual thread
    • DReuse one pooled virtual thread
  4. Which Java version made virtual threads a final (non-preview) feature?

    • AJava 17
    • BJava 19
    • CJava 21
    • DJava 11

Next: Module 07 Mini-Exam. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.