MMS • A N M Bazlur Rahman
Article originally posted on InfoQ. Visit InfoQ
JEP 444, Virtual Threads, was promoted from Candidate to Proposed to Target status for JDK 21. This feature provides virtual threads, lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications on the Java platform. This JEP intends to finalize this feature based on feedback from the previous two rounds of preview: JEP 436, Virtual Threads (Second Preview), delivered in JDK 20; and JEP 425, Virtual Threads (Preview), delivered in JDK 19.
With this JEP, Java now has two types of threads: traditional threads, also called platform threads, and virtual threads. Platform threads are a one-to-one wrapper over operating system threads, while virtual threads are lightweight implementations provided by the JDK that can run many virtual threads on the same OS thread. Virtual threads offer a more efficient alternative to platform threads, allowing developers to handle a large number of tasks with significantly lower overhead. These threads offer compatibility with existing Java code and a seamless migration path to benefit from enhanced performance and resource utilization. Consider the following example:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
The JDK can now run up to 10,000 concurrent virtual threads on a small number of operating system (OS) threads, as little as one, to execute the simple code above that involves sleeping for one second.
Virtual threads are designed to work with thread-local variables and inheritable thread-local variables, just like platform threads. However, due to the large number of virtual threads that can be created, developers should use thread-local variables with caution. To assist with the migration to virtual threads, the JDK provides a system property, jdk.traceVirtualThreadLocals
, that triggers a stack trace when a virtual thread sets the value of any thread-local variable.
The java.util.concurrent
package now includes support for virtual threads. The LockSupport
API has been updated to gracefully park and unpark virtual threads, enabling APIs that use LockSupport
, such as Locks, Semaphores, and blocking queues, to function seamlessly with virtual threads. The Executors.newThreadPerTaskExecutor(ThreadFactory)
and Executors.newVirtualThreadPerTaskExecutor()
methods provide an ExecutorService
that creates a new thread for each task, facilitating the migration and interoperability with existing code that uses thread pools and ExecutorService
.
Networking APIs in the java.net
and java.nio.channels
packages now support virtual threads, enhancing efficiency in concurrent applications. Blocking operations on a virtual thread free up the underlying platform thread, while I/O methods in the Socket
, ServerSocket
and DatagramSocket
classes have been made interruptible. This update promotes consistent behavior and improved performance for Java developers working with concurrent applications.
The java.io
package, which provides APIs for streams of bytes and characters, has been updated to avoid pinning when used in virtual threads. Pinning in virtual threads refers to a lightweight thread being “stuck” to a specific platform thread, limiting concurrency and flexibility due to blocking operations. BufferedInputStream
, BufferedOutputStream
, BufferedReader
, BufferedWriter
, PrintStream
, and PrintWriter
now use an explicit lock instead of a monitor when used directly. The stream decoders and encoders used by InputStreamReader
and OutputStreamWriter
now use the same lock as their enclosing InputStreamReader
or OutputStreamWriter
.
Java Native Interface (JNI) has introduced a new function, IsVirtualThread
, to test if an object is a virtual thread. The JNI specification otherwise remains unchanged.
The debugging architecture, consisting of the JVM Tool Interface (JVM TI), the Java Debug Wire Protocol (JDWP), and the Java Debug Interface (JDI), has been updated to support virtual threads. All three interfaces now support virtual threads, with new capabilities and methods added to handle thread start and end events and bulk suspension and resumption of virtual threads.
JDK Flight Recorder (JFR) now supports virtual threads with new events such as jdk.VirtualThreadStart
, jdk.VirtualThreadEnd
, jdk.VirtualThreadPinned
, and jdk.VirtualThreadSubmitFailed
. These events provide insight into the behavior of virtual threads in the application.
Java Management Extensions (JMX) will continue to support only platform threads through the ThreadMXBean
interface. A new method in the HotSpotDiagnosticsMXBean
interface generates the new-style thread dump to support virtual threads.
While virtual threads bring significant performance improvements, developers should be aware of compatibility risks due to changes in existing APIs and their implementations. Some of these risks include revisions to the internal locking protocol in the java.io
package and source and binary incompatible changes that may impact code that extends the Thread
class.
Virtual threads mark a significant milestone in Java’s journey to support highly concurrent and scalable applications. With a more efficient and lightweight threading model, developers can now handle millions of tasks with ease and better utilize system resources. Developers can leverage more details on JEP 425, which can be found in this InfoQ news story and this JEP Café screen cast by José Paumard, Java developer advocate with the Java Platform Group at Oracle.