Toll Free:

1800 889 7020

Concurrency in Java: Exploring Threads, Virtual Threads, and Their Efficient Usage

In Java, a Thread is a sub-process and is considered the smallest unit of processing that can be scheduled by the operating system. It allows us to perform multiple tasks and functions simultaneously within a program, leveraging the concepts of multitasking and parallel processing. Let us delve into understanding Threading and Virtual Threads in Java.

1. What is Threading

To create a Thread in Java, we can follow one of two approaches: either by extending the Thread class or by implementing the Runnable interface.

1.1 By extending the Thread Class

  • Firstly, we need to create a class that Extends the thread class.
  • Now, Override the run() method in your class which defines the code that contains the new thread’s task.
  • Now create an instance of your class
  • To begin the execution of the thread call the start() method on the instance

1.1.1 Code Example

public class Add extends Thread {
    public void run() {
        System.out.println("thread is now running....");
    }
    public static void main(String args[]) {
        Add A1 = new Add();
        A1.start();
    }    
}

1.2 Implementing the Runnable Interface

  • Firstly, Create a class that can implement the Runnable interface.
  • Now implement the run() method in your class which is defining the task of the thread.
  • Similarly, create an instance of your class.
  • Now create a new Thread object and pass the instance of your class.
  • On the thread object call the start() method.

1.2.1 Code Example

public class Add3 implements Runnable {
    public void run();{
        System.out.println("thread is now running...");
    }

    public static void main(String args[]){
        Add3 A1 = new Add3();
        Thread t1 = new Thread(A1);
        t1.start();
    }
}

2. What is a Virtual Thread?

Java introduced Virtual Threads as part of Project Loom, offering a lightweight alternative to traditional platform threads, which are managed by the operating system. Virtual Threads, managed by the JVM (Java Virtual Machine), are far more efficient and scalable. They make handling thousands or even millions of concurrent tasks much easier. Virtual Threads share a small pool of “real” threads managed by the OS. When a virtual thread needs to execute, it “borrows” a real thread, and if it needs to wait (e.g., during I/O operations), it releases the real thread so other virtual threads can utilize it.

2.1 Key Points

  • Lightweight: Without the overhead associated with OS-level threads, it allows you to create and manage a large number of threads. Also, they are much lighter as compared to Platform threads.
  • Managed by JVM: Instead of depending on the Operating Systems. Java Virtual Machine handles Virtual Threads which results in better resource management, especially when we are dealing with blocking operations like I/O.
  • Scalable & Non-Blocking: Without blocking the actual thread, it can block calls such as I/O operations, which makes it suitable for highly concurrent applications, like Web servers and microservices.

2.1.1 Code Example

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class VirtualThreadsExample {

	public static void main(String[] args) {
    	// Create an ExecutorService for virtual threads
    	ExecutorService executor = Executors.newVirtualThreadExecutor();

    	// Submit multiple tasks to the virtual thread executor
    	for (int j = 0; j < 5; j++) {
        	int taskId = j;
        	executor.submit(() -> {
            	try {
                	// Simulate some work with Thread.sleep
                	System.out.println("Task " + taskId + " is running in " + Thread.currentThread());
                	Thread.sleep(1000); // Simulating work by sleeping
                	System.out.println("Task " + taskId + " completed");
            	} catch (InterruptedException e) {
                	Thread.currentThread().interrupt();
                	System.err.println("Task " + taskId + " was interrupted");
            	}
        	});
    	}

    	// Shut down the executor
    	executor.shutdown();
	}
}

The provided code demonstrates how to use Java Virtual Threads to handle multiple concurrent tasks efficiently. Here’s a breakdown of the code:

  • Firstly we Create ExecutorService for Virtual Threads: ExecutorService executor = Executors.newVirtualThreadExecutor(); creates an executor service specifically designed to manage virtual threads. This allows you to submit tasks that will be executed by virtual threads.
  • Submit Tasks: A for loop is used to submit multiple tasks to the executor service. Each iteration submits a lambda expression that represents a task. The lambda expression captures the loop index (taskId) and prints messages indicating the start and completion of the task.
  • Task Execution: Within each task, System.out.println is used to display the task’s execution status, and Thread.sleep(1000) simulates some work by pausing the thread for one second. If an interruption occurs, the task handles it by setting the thread’s interrupt flag and printing an error message.
  • Shutdown Executor: executor.shutdown(); is called to gracefully shut down the executor service. This ensures that all submitted tasks are completed before the application terminates.

This example illustrates how virtual threads can simplify concurrent programming by managing a large number of tasks with minimal resource overhead, leveraging the capabilities introduced in Java 21 and later.

3. Why Virtual Threads?

Just as we manage multiple tasks concurrently in our daily lives to maximize our limited time, applications often handle numerous concurrent tasks, many of which spend a significant amount of time in a waiting state. A prime example is web servers, which handle a large number of client requests involving blocking I/O operations such as fetching resources. While Virtual Threads don’t necessarily execute code faster than platform threads, they offer superior scalability, enabling applications to handle more tasks simultaneously rather than improving execution speed.

3.1 Scheduling Virtual Threads

The Java runtime determines when and where to run virtual threads. Unlike traditional threads, which are scheduled by the operating system, virtual threads are managed by the Java Virtual Machine (JVM). By default, the scheduling of virtual threads is handled by a java.util.concurrent.ForkJoinPool, which has parallel capacity equivalent to the number of available platform threads, based on the number of processors.

3.1.1 Smart Resource Use

When a virtual thread is waiting for an event (such as a network response), it stops using a real thread. This allows the real thread to be utilized for other tasks, enhancing the overall efficiency of your program.

3.2 Pinned Virtual Threads

Occasionally, a virtual thread needs to remain attached to the same real thread while performing a specific task. This is known as a “pinned” virtual thread, where the virtual thread is fixed or bound to a particular operating system thread. Typically, virtual threads are lightweight and can switch between various OS threads to maximize resource efficiency.

3.2.1 Why Pin a Thread?

Certain tasks, such as interacting with native code or specific system resources, require the thread to remain consistent. In these scenarios, a virtual thread is pinned to a real thread and cannot be swapped out. However, pinning threads can block other tasks, so it should only be done when necessary.

3.3 Platform Threads in Java

Platform threads are the traditional thread type in Java, managed directly by the operating system. Each Java thread corresponds to an OS thread. Here is an overview of platform threads and their use cases:

  • OS-Managed: When you create a thread in Java, the operating system handles its scheduling, including when it runs and pauses.
  • Heavy: Platform threads consume significant system resources, such as memory and CPU, making them “heavy” compared to newer thread types like virtual threads.
  • Limited: Due to their high resource consumption, creating a large number of platform threads can slow down your program.

3.3.1 How Do Platform Threads Work?

  • Scheduling: The OS determines when each thread runs. A thread that is waiting (e.g., for file reading) remains idle, which can be inefficient.
  • Blocking: A platform thread that is blocked still consumes resources, even if it is not performing any active work.

3.3.2 When Should You Use Platform Threads?

  • Heavy Work: For tasks involving extensive calculations and minimal waiting, platform threads are suitable.
  • Native Code: If you need to interact with system-level code or libraries requiring direct OS threads, platform threads are necessary.
  • Special Cases: Some tasks, such as those involving graphical user interfaces, require a consistent thread, where platform threads are useful.

3.3.3 Challenges with Platform Threads

  • Resource-Hungry: Excessive platform threads can slow down your program due to high resource consumption.
  • Complexity: Managing numerous platform threads can be complex, requiring careful coordination to avoid issues like deadlocks.

4 Conclusion

Virtual Threads represent a significant advancement in Java, offering enhanced scalability for handling numerous concurrent tasks. Unlike traditional platform threads, which are resource-intensive and managed by the operating system, Virtual Threads are managed by the JVM, allowing for more efficient use of resources and better handling of high-concurrency scenarios. While Virtual Threads do not increase execution speed, they provide a scalable solution that simplifies managing large numbers of concurrent tasks. For companies engaging in Java outsourcing, Virtual Threads offer the potential for building more scalable and cost-effective solutions by enabling efficient management of resources and high-concurrency demands. Platform threads, while still essential for tasks involving heavy computations, native code, or specific system resources, come with limitations such as high resource consumption and complexity in management. Virtual Threads address these limitations by allowing more lightweight and flexible handling of concurrent tasks, making them particularly useful for applications with high throughput requirements. In summary, adopting Virtual Threads can lead to more efficient and scalable applications, particularly in scenarios involving numerous simultaneous tasks. However, platform threads continue to play a crucial role in scenarios requiring direct OS interaction or intensive computational tasks.

Cleveland

Scroll to Top