Sunday, December 8, 2024

Virtual Threads in Java 21: Simplified Concurrency for Modern Applications

 

With Java 21, Virtual Threads have redefined how we approach concurrency, offering a lightweight and efficient way to handle parallel and asynchronous tasks. Unlike CompletableFuture or ParallelStream, Virtual Threads allow developers to write simple, synchronous-looking code while achieving the scalability of asynchronous solutions.

Key Features of Virtual Threads:

  • Lightweight Concurrency: Virtual Threads are cheap to create and manage, enabling applications to handle thousands or even millions of concurrent tasks.
  • Simpler Programming Model: Code written with Virtual Threads resembles traditional synchronous code, making it easier to understand and maintain.
  • Automatic Thread Management: The JVM manages Virtual Threads' scheduling, eliminating the need to manually manage thread pools.
  • Great for IO-Bound Tasks: Virtual Threads excel in handling IO-bound workloads, such as making multiple HTTP calls concurrently.

Example: Virtual Threads with HTTP Requests

Here’s how you can use Virtual Threads to fetch posts concurrently:

Example: Fetch Posts Using Virtual Threads


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.*;

public class VirtualThreadExample {

    public static void main(String[] args) {
        try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
            // List of posts to process
            List<Integer> posts = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
            long start = System.nanoTime();

            // Submit a task for each post
            List<Future<Object>> futures = posts.stream()
                    .map(post -> myExecutor.submit(() -> {
                        getResponse("https://jsonplaceholder.typicode.com/posts/" + post);
                        return null; // Explicitly return null for Future<Void>
                    }))
                    .toList();

            // Wait for all tasks to complete
            for (Future<Object> future : futures) {
                future.get(); // Ensures task completion
            }

            long duration = (System.nanoTime() - start) / 1_000_000;
            System.out.printf("Processed %d posts in %d millis%n", posts.size(), duration);
            System.out.println("Program Completed !!");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void getResponse(String urlRest) {
        try {

            InputStream is = getInputStream(urlRest);

            BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = rd.readLine()) != null) {
                response.append(line);
            }
            rd.close();
            System.out.println("Response: " + response.toString().trim());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static InputStream getInputStream(String urlRest) throws IOException {
        URL url = new URL(urlRest);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("Content-Language", "en-US");
        connection.setConnectTimeout(60000);
        connection.setUseCaches(false);
        connection.setDoInput(true);
        connection.setDoOutput(true);

        return connection.getResponseCode() >= 400
                ? connection.getErrorStream()
                : connection.getInputStream();
    }
}

Performance and Simplicity

Output:

Processed 20 posts in 971 millis
Program Completed !!


Scalability: Virtual Threads scale seamlessly for handling a large number of requests.

Readability: The synchronous-looking code is easy to understand and maintain.

The ExecutorService created using Executors.newVirtualThreadPerTaskExecutor() in the try-with-resources block will automatically be shut down when the block is exited. This is because ExecutorService implements the AutoCloseable interface, and in a try-with-resources block, the close() method is automatically called.

if you're using the ExecutorService outside of a try-with-resources block, you must explicitly shut it down. This ensures that all threads are properly terminated and resources are released.






Why Virtual Threads?
  1. No Thread Pool Management: Virtual Threads handle concurrency without the need for explicit thread pools or tuning.
  2. Efficient Resource Usage: They use far less memory compared to platform threads.
  3. Modern and Forward-Looking: As of Java 21, Virtual Threads are the future of Java concurrency, making them the ideal choice for most applications.

If you’re still on Java 8 or 17, you might use CompletableFuture or ParallelStream. But with Java 21, Virtual Threads are the most straightforward and efficient way to handle concurrent tasks.








Virtual Threads in Java 21: Simplified Concurrency for Modern Applications

  With Java 21, Virtual Threads have redefined how we approach concurrency, offering a lightweight and efficient way to handle parallel and ...