Virtual thread traps to avoid
Three habits from the platform-thread era that quietly destroy virtual-thread performance.
Open this lesson in the learning hubKey points
- Do not pool them. They are cheap to create; a pool caps the very thing that made them useful.
- Pinning: blocking inside
synchronizedpins the virtual thread to its carrier. UseReentrantLock. - ThreadLocal: fine for a few hundred threads, wasteful across a million. Scoped values are the answer.
- CPU-bound work gains nothing - virtual threads help when you are waiting, not when you are computing.
- Run with
-Djdk.tracePinnedThreads=fullto see exactly where you are pinned.
Example
import java.util.concurrent.locks.ReentrantLock;
public class Main {
static final Object MONITOR = new Object();
static final ReentrantLock LOCK = new ReentrantLock();
static int viaSync = 0, viaLock = 0;
public static void main(String[] args) throws Exception {
// synchronized: correct, but a blocking call inside it PINS the carrier
Thread a = Thread.ofVirtual().start(() -> {
for (int i = 0; i < 1000; i++) {
synchronized (MONITOR) { viaSync++; }
}
});
// ReentrantLock: the virtual thread can unmount while waiting
Thread b = Thread.ofVirtual().start(() -> {
for (int i = 0; i < 1000; i++) {
LOCK.lock();
try { viaLock++; } finally { LOCK.unlock(); }
}
});
a.join();
b.join();
System.out.println("synchronized count : " + viaSync);
System.out.println("ReentrantLock count: " + viaLock);
System.out.println("isVirtual : " + Thread.ofVirtual().unstarted(() -> {}).isVirtual());
}
}
Virtual threads punish pooling and synchronized - the two habits platform threads taught you.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Java 21 Course course, and every lesson in it is listed on the Java 21 Course contents page.