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
Please enter at least three characters to search
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

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

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

  • Designing a Java Connector for Software Integrations
  • Memory-Optimized Tables: Implementation Strategies for SQL Server
  • When Airflow Tasks Get Stuck in Queued: A Real-World Debugging Story
  • Designing AI Multi-Agent Systems in Java
  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.3K 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
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!