Java Virtual?Threads

Java Virtual?Threads

Java introduced a new concept of Virtual Threads as part of Project Loom, an effort to improve concurrency in Java applications. This guide explains the Virtual Threads in Java, their purpose, and their benefits. It will also walk you through examples of how to use them.

What are Virtual Threads?

Virtual Threads (previously known as ‘fibers’) are lightweight, user-mode threads managed entirely by the Java runtime, not by the operating system. They are cheap to create and easy to manage, and they help developers to write more straightforward and efficient concurrent code.

Virtual Threads are designed to make it easier for software developers to write concurrent applications by adding a simpler concurrency model that’s easier to understand and reason about yet is very efficient in terms of system resources.

This is a significant departure from the traditional model of one operating system thread per Java thread, where creating thousands or millions of threads can lead to substantial resource usage and performance problems.

Why Use Virtual Threads?

Virtual Threads are extremely lightweight. You can create millions of virtual threads in a single JVM, which is not feasible with traditional threads due to high memory overhead. They also allow developers to use a simpler programming model based on blocking I/O and locks, reducing the complexity of non-blocking I/O and synchronization.

Creating Virtual Threads in Java

Virtual Threads can be created in Java using the Thread.startVirtualThread method. Here is an example:

Thread.startVirtualThread(() -> {
    System.out.println("Hello, World from a Virtual Thread!");
});        

This will start a new virtual thread that executes the provided Runnable. Unlike traditional threads, note that there is no need to call a start() method.

Example: Multiple Virtual Threads

Here is an example where we create 10,000 virtual threads:

for (int i = 0; i < 10_000; i++) {
    final int index = i;
    Thread.startVirtualThread(() -> {
        System.out.println("Hello, World from Virtual Thread " + index + "!");
    });
}        

This will start 10,000 virtual threads. Each Thread will print a message to the console. This would be highly resource-intensive with traditional threads, but it is easily feasible with virtual threads.

Working with Virtual Threads and Executors

You can also use an Executor to manage a pool of virtual threads. Here is an example:

var executor = Executors.newVirtualThreadExecutor();

executor.execute(() -> {
    System.out.println("Hello, World from a Virtual Thread via Executor!");
});

executor.close();        

In this example, the newVirtualThreadExecutor method returns an Executor that manages a pool of virtual threads. We then use the execute method to run a task on one of the virtual threads.

Blocking Calls in Virtual Threads

One of the advantages of virtual threads is that they handle blocking calls much better than traditional threads. In the following example, the virtual Thread will block for 5 seconds:

Thread.startVirtualThread(() -> {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Hello, World from a Virtual Thread after sleeping!");
});        

With traditional threads, blocking a thread like this could lead to performance problems on a large scale, as each Thread consumes significant system resources. But with virtual threads, blocking costs are much lower, making it feasible to use more straightforward and intuitive blocking-style code even in highly concurrent applications.

Managing Virtual Threads

In the previous examples, we did not keep a reference to the virtual threads we created, so we could not manage them directly after creation. But you can also keep a reference to a virtual thread and use it to manage the Thread:

Thread thread = Thread.startVirtualThread(() -> {
    System.out.println("Hello, World from a Managed Virtual Thread!");
});

// Call methods on the thread reference...        

Once you have a reference to the Thread, you can call methods on it just like with a traditional thread. For example, you can check if the Thread is alive, wait for it to finish using the join method, interrupt it, etc.

Handling Exceptions in Virtual Threads

Exceptions in virtual threads can be handled in the same way as in traditional threads. Uncaught exceptions will terminate the Thread, but you can catch exceptions in the run method to handle them:

Thread.startVirtualThread(() -> {
    try {
        // Code that might throw an exception
    } catch (Exception e) {
        // Handle the exception
    }
});        

You can also set an uncaught exception handler for a virtual thread:

Thread thread = Thread.startVirtualThread(() -> {
    // Code that might throw an exception
});
thread.setOnUncaughtExceptionHandler((t, e) -> {
    // Handle the exception
});        

Differences Between Virtual and Regular Threads

While the programming model for virtual threads is very similar to that for regular threads, there are some differences:

  1. Scheduling: Virtual threads are not scheduled by the operating system but by the Java runtime. This makes context switches between virtual threads much cheaper than between regular threads.
  2. Daemon Status: All virtual threads are daemon threads. This means that the JVM will exit when all regular threads have finished, even if virtual threads are still running.
  3. Thread Locals: Because virtual threads can be reused across different tasks, they are not suitable for storing thread-local data.
  4. Resource Usage: Virtual threads are designed to have a much lower resource usage than regular threads, making it feasible to have millions of them in a single application.

Monitoring and Debugging Virtual Threads

Java’s built-in monitoring and debugging tools have been updated to work with virtual threads. This includes the jstack tool for examining stack traces, the jstat tool for statistics, and the JMX API for monitoring and managing Java applications.

The ThreadMXBean interface, used for thread monitoring, has been updated to include methods for working with virtual threads. For example, you can use the getVirtualThreadCount method to get the number of live virtual threads, similar to how you would use the getThreadCount method for traditional threads.

Debugging virtual threads with a standard debugger should work as expected. Breakpoints, step-through execution, and inspection of variables should all function normally.

Working with IO in Virtual Threads

When working with IO, blocking operations in virtual threads will not block the underlying kernel thread, allowing other virtual threads to continue running. This means you can use simple, straightforward blocking IO code without worrying about thread pool exhaustion.

This contrasts with traditional threads, where blocking IO can lead to thread starvation and decreased application throughput. As a result, many applications have turned to non-blocking IO, which can be more complex to work with.

Stay tuned, and happy coding!

Visit my Blog for more articles, news, and software engineering stuff!

Follow me on Medium , LinkedIn , and Twitter .

All the best,

Luis Soares

Senior Java Engineer | Tech Lead | AWS Solutions Architect | Rust | Golang | Java | TypeScript | Web3 & Blockchain

#java #threads #multithreading #concurrency #scalability #performance #backend #virtualthreads #architecture #softwaredevelopment #coding #software #development #building #architecture

Munir Ahmad

Scalable Systems & Microservices, Cloud and Databases

6 个月

??

回复

要查看或添加评论,请登录

Luis Soares, M.Sc.的更多文章

  • Zero-Knowledge Proof First Steps - New Video!

    Zero-Knowledge Proof First Steps - New Video!

    In today’s video, we’re diving straight into hands-on ZK proofs for Blockchain transactions! ??? Whether you’re new to…

    1 条评论
  • Your Next Big Leap Starts Here

    Your Next Big Leap Starts Here

    A mentor is often the difference between good and great. Many of the world’s most successful personalities and industry…

    8 条评论
  • Building a VM with Native ZK Proof Generation in?Rust

    Building a VM with Native ZK Proof Generation in?Rust

    In this article we will build a cryptographic virtual machine (VM) in Rust, inspired by the TinyRAM model, using a…

    1 条评论
  • Understanding Pinning in?Rust

    Understanding Pinning in?Rust

    Pinning in Rust is an essential concept for scenarios where certain values in memory must remain in a fixed location…

    10 条评论
  • Inline Assembly in?Rust

    Inline Assembly in?Rust

    Inline assembly in Rust, specifically with the macro, allows developers to insert assembly language instructions…

    1 条评论
  • Building a Threshold Cryptography Library in?Rust

    Building a Threshold Cryptography Library in?Rust

    Threshold cryptography allows secure splitting of a secret into multiple pieces, called “shares.” Using a technique…

    2 条评论
  • Building a ZKP system from scratch in Rust

    Building a ZKP system from scratch in Rust

    New to zero-knowledge proofs? This is part of my ZK Proof First Steps series, where we’re building a ZKP system from…

    4 条评论
  • A Memory Dump Analyzer in?Rust

    A Memory Dump Analyzer in?Rust

    Analyzing binary files and memory dumps is a common task in software development, especially in cybersecurity, reverse…

    2 条评论
  • No more paywalls - I am launching my new Blog + Software Engineering Podcast!

    No more paywalls - I am launching my new Blog + Software Engineering Podcast!

    ?? Exciting News! ?? I’m thrilled to announce the launch of my brand-new software engineering blog/website! ???? It’s…

    6 条评论
  • Understanding Partial Equivalence in Rust's Floating-Point Types

    Understanding Partial Equivalence in Rust's Floating-Point Types

    When working with numeric types in programming, we generally assume that numbers behave in ways that are predictable…

社区洞察

其他会员也浏览了