Pinning Pitfalls: When Virtual Threads Get Stuck
The Promise of Virtual Threads
Virtual threads (Project Loom) promised to liberate us from the constraints of OS threads. By mapping many virtual threads to a few carrier threads, we can achieve high throughput for IO-bound workloads. However, there is a catch: Pinning.
What is Pinning?
A virtual thread is said to be "pinned" to its carrier thread when it executes a synchronized block or method,
or when it performs a native call via JNI. During this time, the virtual thread cannot be unmounted from the carrier thread,
even if it performs a blocking operation.
If a pinned virtual thread blocks (e.g., waiting for a lock or IO), it holds onto the carrier thread, preventing it from serving other virtual threads. This effectively reduces the parallelism of your application and can lead to starvation if too many carrier threads are pinned simultaneously.
Visualizing the "Sticky Gear" Effect
Imagine the scheduler as a system of gears driving tasks. A pinned thread is like a sticky gear that refuses to rotate smoothly or disengage, causing friction in the system.
Figure 1: Simulation of a carrier thread (gear) getting "stuck" due to pinning.
Avoiding the Pitfall
- Use ReentrantLock: Instead of
synchronized, usejava.util.concurrent.locks.ReentrantLock, which allows virtual threads to be unmounted while waiting. - Check Library Compatibility: Ensure third-party libraries (like JDBC drivers) are updated to be virtual-thread friendly.
- Detect Pinning: Use
-Djdk.tracePinnedThreads=fullto identify where pinning occurs in your application.