Careeroza
Pinning and Best Practices
Pinning
A virtual thread is pinned to its carrier (cannot unmount) when it blocks inside a synchronized block or a native method. In Java 21 this wastes a carrier thread and can starve others. (Java 24, JEP 491, removed the synchronized pinning, but Java 21 LTS still has it.)
// Java 21: pins the carrier during the HTTP call
synchronized (this) {
response = httpClient.send(request, BodyHandlers.ofString());
}
// Better on Java 21: ReentrantLock lets the virtual thread unmount
lock.lock();
try {
response = httpClient.send(request, BodyHandlers.ofString());
} finally {
lock.unlock();
}Example
Detect pinning while testing:
java -Djdk.tracePinnedThreads=full -jar app.jarExample output
Thread[#29,ForkJoinPool-1-worker-1,5,CarrierThreads]
java.base/java.lang.VirtualThread.parkOnCarrierThread(VirtualThread.java:...)
com.shop.PriceClient.fetch(PriceClient.java:42) <== monitors:1Best Practices
- Do not pool virtual threads. Use
newVirtualThreadPerTaskExecutor(). - Limit access to scarce resources (DB connections, rate-limited APIs) with a
Semaphoreor the connection pool, not by limiting threads. - Avoid long
synchronizedblocks around I/O on Java 21; useReentrantLock. - Keep
ThreadLocalvalues small; considerScopedValue(preview in 21). - Do not use them for CPU-heavy work; use a fixed pool sized to the cores or parallel streams.
Spring Boot
Example
Spring Boot 3.2+ runs Tomcat request handling, @Async and scheduled tasks on virtual threads with one property (Java 21 needed).
application.properties
spring.threads.virtual.enabled=true