Java / Java Concurrency and Multi Threading in Java 17 and Java 21 Interview questions
Why does a synchronized block pin a virtual thread to its carrier thread?
Normally, when a virtual thread blocks, the JVM unmounts it from its carrier and frees that carrier to run other virtual threads. Pinning is the exception: while a virtual thread is inside a synchronized block or method, or executing a native method or foreign function call, blocking there does not unmount it, it stays pinned to its current carrier thread for that block's whole duration.
synchronized (lock) { someBlockingCall(); // virtual thread stays pinned to its carrier here }
This happens because the JVM's monitor implementation for synchronized is tied to the OS-level thread that acquired it; safely unmounting would mean detaching monitor ownership from that carrier, which the current HotSpot monitor implementation doesn't support. So instead of unmounting, the runtime just blocks the carrier thread itself.
Occasional short pinning is harmless, but if many virtual threads pin for a long time inside synchronized blocks that also do I/O, the small, fixed pool of carrier threads can all end up blocked simultaneously, starving other virtual threads of a carrier to run on and eliminating the scalability benefit virtual threads exist to provide.
The practical fix is to replace long-held synchronized blocks around blocking calls with java.util.concurrent.locks.ReentrantLock, which does not pin, since it's implemented purely in terms of park/unpark rather than a JVM monitor tied to the OS thread.
More Related questions...