Featured image of post Pinning in Java 21 Virtual Threads

Pinning in Java 21 Virtual Threads

Introduction

Virtual threads are introduced in Java 21. The Java architect believes the reactive programming would be replaced by virtual threads at last. When we used synchronized and native code in virtual threads, there is a chance of having deadlocks and the application would become unresponsive. We will talk about this event and how to avoid it from happening.

What happened?

We used synchronized code in virtual threads for a blocking task like this:

synchronized(lockObject) {
    frequentIO();
}

However, the thread scheduler would block a OS thread. This is called as pinning. If the task is long enough, there is a chance that all the OS threads are waiting for the lock released, but the virtual thread notified by the releasing lock has no resource to acquire the lock.

Similarly it happens when using native code, all threads are waiting but the next waiter has no way to run. Both cases lead to a deadlock scenario and the application becomes unresponsive.

You can detected by using JDK Flight Recorder (JFR), which emits jdk.VirtualThreadPinned if the threads holds longer than 20ms by default.

Implementation

The adoption guide addressed a limitation and suggested an alternative for executing a long-lived and frequent code block, by using an explicit lock:

lock.lock();
try {
    frequentIO();
} finally {
    lock.unlock();
}

This limitation would be resolved in Java 24+ by JEP 491.

Wrapping up

We have addressed the limitation of virtual threads in Java 21. We also showed another way to use virtual threads for time-consuming code blocks. It is worth performing a load test across your service to pinpoint bottlenecks before they lead to downtime. As virtual threads are gaining popularity, we should not ignoring the updates of it.

References

Credits

Cover photo by amirali mirhashemian on Unsplash