DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Java Virtual Threads and Scaling
  • Java Thread Dump Analysis
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • The Energy Efficiency of JVMs and the Role of GraalVM

Trending

  • My LLM Journey as a Software Engineer Exploring a New Domain
  • Concourse CI/CD Pipeline: Webhook Triggers
  • Breaking Bottlenecks: Applying the Theory of Constraints to Software Development
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  1. DZone
  2. Coding
  3. Java
  4. Virtual Threads: A Game-Changer for Concurrency

Virtual Threads: A Game-Changer for Concurrency

In this blog, we'll explore Java Virtual Threads, compare them to traditional platform threads, and provide example code to highlight the differences.

By 
Gautham Krishnan user avatar
Gautham Krishnan
·
Sweetty P Devassy user avatar
Sweetty P Devassy
·
Jul. 16, 24 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
13.0K Views

Join the DZone community and get the full member experience.

Join For Free

Despite being nearly 30 years old, the Java platform remains consistently among the top three most popular programming languages. This enduring popularity can be attributed to the Java Virtual Machine (JVM), which abstracts complexities such as memory management and compiles code during execution, enabling unparalleled internet-level scalability.

Java's sustained relevance is also due to the rapid evolution of the language, its libraries, and the JVM. Java Virtual Threads, introduced in Project Loom, which is an initiative by the OpenJDK community, represent a groundbreaking change in how Java handles concurrency. 


The Complete Java Coder Bundle.*

*Affiliate link. See Terms of Use.

Exploring the Fabric: Unveiling Threads

A thread is the smallest schedulable unit of processing, running concurrently and largely independently of other units. It's an instance of java.lang.Thread. There are two types of threads: platform threads and virtual threads.

A platform thread is a thin wrapper around an operating system (OS) thread, running Java code on its underlying OS thread for its entire lifetime. Consequently, the number of platform threads is limited by the number of OS threads. These threads have large stacks and other OS-managed resources, making them suitable for all task types but potentially limited in number.

Virtual threads in Java, unlike platform threads, aren't tied to specific OS threads but still execute on them. When a virtual thread encounters a blocking I/O operation, it pauses, allowing the OS thread to handle other tasks. Similar to virtual memory, where a large virtual address space maps to limited RAM, Java's virtual threads map many virtual threads to fewer OS threads. They're ideal for tasks with frequent I/O waits but not for sustained CPU-intensive operations. Hence virtual threads are lightweight threads that simplify the development, maintenance, and debugging of high-throughput concurrent applications. 

Comparing the Threads of Fabric: Virtual vs. Platform

Let’s compare platform threads with virtual threads to understand their differences better.

Crafting Virtual Threads

Creating Virtual Threads Using Thread Class and Thread.Builder Interface

The example below creates and starts a virtual thread that prints a message. It uses the join method to ensure the virtual thread completes before the main thread terminates, allowing you to see the printed message.

Java
 
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello World!! I am Virtual Thread"));
thread.join();


The Thread.Builder interface allows you to create threads with common properties like thread names. The Thread.Builder.OfPlatform subinterface creates platform threads, while Thread.Builder.OfVirtual creates virtual threads.

Here’s an example of creating a virtual thread named "MyVirtualThread" using the Thread.Builder interface:

Java
 
Thread.Builder builder = Thread.ofVirtual().name("MyVirtualThread");
Runnable task = () -> {
    System.out.println("Thread running");
};
Thread t = builder.start(task);
System.out.println("Thread name is: " + t.getName());
t.join();


Creating and Running a Virtual Thread Using Executors.newVirtualThreadPerTaskExecutor() Method

Executors allow you to decouple thread management and creation from the rest of your application.

In the example below, an ExecutorService is created using the Executors.newVirtualThreadPerTaskExecutor() method. Each time ExecutorService.submit(Runnable) is called, a new virtual thread is created and started to execute the task. This method returns a Future instance. It's important to note that the Future.get() method waits for the task in the thread to finish. As a result, this example prints a message once the virtual thread's task is completed.

Java
 
try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));
    future.get();
    System.out.println("Task completed");
    // ...


Is Your Fabric Lightweight With Virtual Threads?

Memory

Program 1: Create 10,000 Platform Threads

Java
 
public class PlatformThreadMemoryAnalyzer {

    private static class MyTask implements Runnable {

        @Override
        public void run() {
            try {
                // Sleep for 10 minutes
                Thread.sleep(600000);
            } catch (InterruptedException e) {
                System.err.println("Interrupted Exception!!");
            }
        }
    }

    public static void main(String args[]) throws Exception {
        // Create 10000 platform threads
        int i = 0;
        while (i < 10000) {
            Thread myThread = new Thread(new MyTask());
            myThread.start();
            i++;
        }
        Thread.sleep(600000);
    }
}


Program 2: Create 10,000 Virtual Threads

Java
 
public class VirtualThreadMemoryAnalyzer {

    private static class MyTask implements Runnable {

        @Override
        public void run() {
            try {
                // Sleep for 10 minutes
                Thread.sleep(600000);
            } catch (InterruptedException e) {
                System.err.println("Interrupted Exception!!");
            }
        }
    }

    public static void main(String args[]) throws Exception {
        // Create 10000 virtual threads
        int i = 0;
        while (i < 10000) {
            Thread.ofVirtual().start(new Task());
            i++;
        }
        Thread.sleep(600000);
    }
}


Executed both programs simultaneously in a RedHat VM. Configured the thread stack size to be 1mb (by passing JVM argument -Xss1m). This argument indicates that every thread in this application should be allocated 1mb of stack size. Below is the top command output of the threads running.

VIRTYou can notice that the virtual threads only occupies 7.8mb (i.e., 7842364 bytes), whereas the platform threads program occupies 19.2gb. This clearly indicates that virtual threads consume comparatively much less memory.

Thread Creation Time

Program 1: Launches 10,000 platform threads

Java
 
public class PlatformThreadCreationTimeAnalyzer {

    private static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println("Hello! I am a Platform Thread");
        }
    }

    public static void main(String[] args) throws Exception {
        long startTime = System.currentTimeMillis();
        for (int counter = 0; counter < 10_000; ++counter) {
            new Thread(new Task()).start();
        }
        System.out.print("Platform Thread Creation Time: " + (System.currentTimeMillis() - startTime));
    }
}


Program 2: Launches 10,000 virtual threads

Java
 
public class VirtualThreadCreationTimeAnalyzer {

    private static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println("Hello! I am a Virtual Thread");
        }
    }

    public static void main(String[] args) throws Exception {
        long startTime = System.currentTimeMillis();
        for (int counter = 0; counter < 10_000; ++counter) {
            Thread.startVirtualThread(new Task());
        }
        System.out.print("Virtual Thread Creation Time: " + (System.currentTimeMillis() - startTime));
    }

}


Below is the table that summarizes the execution time of these two programs:


Virtual Threads

Platform Threads

Execution Time

84 ms

346 ms


You can see that the virtual Thread took only 84 ms to complete, whereas the Platform Thread took almost 346 ms. It’s because platform threads are more expensive to create. Because whenever a platform needs to be created an operating system thread needs to be allocated to it. Creating and allocating an operating system thread is not a cheap operation.

Reweaving the Fabric: Applications of Virtual Threads

Virtual threads can significantly benefit various types of applications, especially those requiring high concurrency and efficient resource management. Here are a few examples:

  1. Web servers: Handling a large number of simultaneous HTTP requests can be efficiently managed with virtual threads, reducing the overhead and complexity of traditional thread pools.
  2. Microservices: Microservices often involve a lot of I/O operations, such as database queries and network calls. Virtual threads can handle these operations more efficiently.
  3. Data processing: Applications that process large amounts of data concurrently can benefit from the scalability of virtual threads, improving throughput and performance.

Weaving Success: Avoiding Pitfalls

To make the most out of virtual threads, consider the following best practices:

  1. Avoid synchronized blocks/methods: When using virtual threads with synchronized blocks, they may not relinquish control of the underlying OS thread when blocked, limiting their benefits.
  2. Avoid thread pools for virtual threads: Virtual threads are meant to be used without traditional thread pools. The JVM manages them efficiently, and thread pools can introduce unnecessary complexity.
  3. Reduce ThreadLocal usage: Millions of virtual threads with individual ThreadLocal variables can rapidly consume Java heap memory.

Wrapping It Up

Virtual threads in Java are threads implemented by the Java runtime, not the operating system. Unlike traditional platform threads, virtual threads can scale to a high number — potentially millions — within the same Java process. This scalability allows them to efficiently handle server applications designed in a thread-per-request style, improving concurrency, throughput, and hardware utilization.

Developers familiar with java.lang.Thread since Java SE 1.0 can easily use virtual threads, as they follow the same programming model. However, practices developed to manage the high cost of platform threads are often counterproductive with virtual threads, requiring developers to adjust their approach. This shift in thread management encourages a new perspective on concurrency.

"Hello, world? Hold on, I’ll put you on hold, spawn a few more threads, and get back to you"

Happy coding. :)
Java virtual machine Java (programming language) Thread pool

Opinions expressed by DZone contributors are their own.

Related

  • Java Virtual Threads and Scaling
  • Java Thread Dump Analysis
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • The Energy Efficiency of JVMs and the Role of GraalVM

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: