Lab 02: Virtual Threads

Objective

Master Java 21 Virtual Threads: Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor(), 1000-task scale demos, parallel I/O-bound work, the "first-to-succeed" pattern, and why ReentrantLock should replace synchronized to avoid virtual thread pinning.

Background

Virtual threads (JEP 444, Java 21) are lightweight user-mode threads managed by the JVM, not the OS. You can create millions of them — each blocking I/O call parks the virtual thread and unmounts it from its carrier thread, freeing the carrier to run other virtual threads. This eliminates the need for reactive/async code for I/O-bound applications.

Time

30 minutes

Prerequisites

  • Practitioner Lab 05 (Concurrency)

Tools

  • Docker: zchencow/innozverse-java:latest


Lab Instructions

Steps 1–8: 1000 virtual tasks, parallel price fetch, first-to-succeed, Thread.ofVirtual API, virtual vs platform, pinning avoidance, Capstone

💡 Virtual threads pin to their carrier when inside a synchronized block. A pinned virtual thread cannot be unmounted — it holds its OS carrier thread blocked, defeating the entire purpose. Java 21 fixes this for ReentrantLock. Rule: replace synchronized with ReentrantLock in virtual-thread code. In Java 24+ synchronized no longer pins (JEP 491), but for Java 21 LTS this is critical.

📸 Verified Output:


Summary

API
Purpose

Executors.newVirtualThreadPerTaskExecutor()

One virtual thread per task

Thread.ofVirtual().name("x").start(r)

Named virtual thread

Thread.ofPlatform().start(r)

Explicit platform thread

thread.isVirtual()

Check thread type

ReentrantLock

Lock without pinning carrier

CountDownLatch(n)

Await N completions

Further Reading

Last updated