Sections in this lesson
Virtual Threads
Use Java 21 virtual threads for blocking workloads while preserving resource limits and cancellation.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Explain concurrency versus CPU parallelism.
- Limit scarce resources without pooling virtual threads.
- Handle completion, interruption and cleanup explicitly.
Before you start
The flow
- 1.Create tasks
- 2.Acquire permit
- 3.Wait or perform I/O
- 4.Release
- 5.Join
Scale waiting, not CPU speed
Virtual threads let the runtime multiplex many Java threads over a smaller set of operating-system threads. They are useful for workloads dominated by supported blocking I/O. They do not make CPU-bound calculations faster or increase the number of available cores.
Use a task-per-virtual-thread executor instead of constructing a fixed pool of virtual threads. Keep CPU-intensive work bounded separately. Measure throughput and tail latency with the real workload.
Connections are still finite
A million lightweight tasks do not create a million database connections. Keep connection pools, semaphores, queue limits and deadlines at the resource boundary. The example starts 32 virtual threads but permits only three simultaneous resource users.
The latch makes concurrency checks deterministic rather than depending on sleep timing. A finally block releases permits even on interruption. Time-bounded waits prevent a broken example from hanging indefinitely.
Inspect version-specific constraints
On the Java 21 baseline, blocking while holding a monitor can pin a virtual thread to its carrier. Later JDKs change some pinning behavior; do not carry a Java 21 diagnosis into every newer runtime unchanged. Audit native calls and thread-local footprints and use profiling before rewriting synchronization.
Try it
import java.util.ArrayList;import java.util.concurrent.CountDownLatch;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;public class VirtualThreads { public static void main(String[] args) throws Exception { var permits = new Semaphore(3); var ready = new CountDownLatch(3); var release = new CountDownLatch(1); var active = new AtomicInteger(); var peak = new AtomicInteger(); var tasks = new ArrayList<Future<Boolean>>(); try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 32; i++) { tasks.add(executor.submit(() -> { permits.acquire(); try { peak.accumulateAndGet(active.incrementAndGet(), Math::max); ready.countDown(); release.await(); return Thread.currentThread().isVirtual(); } finally { active.decrementAndGet(); permits.release(); } })); } try { if (!ready.await(5, TimeUnit.SECONDS)) { throw new AssertionError("Workers did not reach the resource boundary"); } } finally { release.countDown(); } for (var task : tasks) { if (!task.get(5, TimeUnit.SECONDS)) throw new AssertionError("Not virtual"); } } if (peak.get() != 3 || active.get() != 0 || permits.availablePermits() != 3) { throw new AssertionError("Resource limit or cleanup failed"); } System.out.println("32 virtual threads; at most 3 concurrent resource users."); }}Run npm run examples:java with JDK 21+. Uses latches rather than timing-based speed assertions.
Interview questions
Will virtual threads accelerate sorting?
Not inherently. CPU-bound parallelism is constrained by available cores and algorithm overhead.
Why keep a semaphore?
The downstream service or pool has finite capacity even when waiting tasks are cheap.
What happens on interruption?
Let interruptible waits terminate and release resources in finally; do not swallow the signal and continue indefinitely.
Practice
Add an interrupted task and verify that the semaphore recovers all permits.
Check your solution
- Every worker is virtual.
- Peak resource use never exceeds three.
- Cancellation does not leak permits.
Official references
Continue learning
Category sequence
Java
Lesson 42 / 42
Was this lesson useful?
Change notes: Phase 2: bilingual lesson with versioned executable reference.