JDK 24 Makes Virtual Threads Even Better (No More Pinning!)

Ashikur

9 May, 2025

Java’s new virtual threads made it easier to build fast, scalable apps. But in JDK 21, there was one big problem — if your code used synchronized methods or blocks, virtual threads didn’t work as well as they should.

Now with JDK 24, that problem is fixed. Let’s take a look at what changed and why it matters — even for older codebases!

What Are Virtual Threads?

In JDK 21, Java introduced virtual threads — lightweight threads that are much faster and more efficient than traditional (platform) threads. You can run thousands of virtual threads without using too much memory or CPU.

But there was a catch…

The Problem in JDK 21: Pinning

In JDK 21, if a virtual thread entered a synchronized method and got blocked (like waiting on I/O or a database), it would get “pinned” to a platform thread.

That means the platform thread had to sit and wait — it couldn’t help other virtual threads — which defeated the whole point of using virtual threads.

🍽 A Simple Analogy

Imagine a busy restaurant:

  • JDK 21: A waiter (platform thread) is stuck standing next to one customer (virtual thread) the whole time they’re waiting for food. No other customers can be served.

  • JDK 24: The waiter gives the customer a pager and helps other customers. When the food is ready, any waiter can serve it.

The Fix in JDK 24: No More Pinning

Thanks to JEP 491, JDK 24 allows virtual threads to use synchronized code without getting pinned, as long as different threads are locking on different objects.

That means:

  • You can still use synchronized blocks or methods
  • You get all the performance benefits of virtual threads
  • Legacy code (older codebases) can work better without major rewrites
What is synchronized in Java?

synchronized is used to protect shared data so only one thread can use it at a time.

1. Synchronizing the Whole Method

				
					public class MethodSynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
				
			

2. Synchronizing Just a Block of Code

				
					public class BlockSynchronizedCounter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
}
				
			
How Much Faster Is JDK 24?

Example 1: Simple Java Benchmark

				
					for (int i = 0; i < 5000; i++) {
    final Object lock = new Object();
    executor.submit(() -> {
        try {
            doCpuWork(); // Math operations

            synchronized (lock) {
                Thread.sleep(5); // Blocking call
            }
        } catch (Exception e) {
            // Handle error
        }
    });
}
				
			

Results:

  • 🐢 JDK 21: 31.791 seconds
  • 🚀 JDK 24: 0.454 seconds
How to Use Virtual Threads in Spring Boot

Enabling virtual threads in Spring Boot is straightforward. For Spring Boot 3.2 and later, add this property to your application.properties:

				
					spring.threads.virtual.enabled=true
				
			
Best Practices for Virtual Threads with synchronized
  • Use a different lock for each resource
  • Avoid using this or static locks
  • Prefer built-in thread-safe tools
  • Be consistent and test thoroughly
Thinking About Migration?
  • Find synchronized code with blocking operations
  • Refactor to use per-resource locks
  • Test well
  • Check library compatibility
Final Thoughts

JDK 24 brings a big win:

  • Virtual threads now work great with synchronized code
  • Older apps can now scale better without big changes
  • You get better performance with just a small update
Ashikur

9 May, 2025