A Deep Dive into Java Multithreading
In the world of Java programming, multithreading is a powerful concept that allows developers to create highly efficient and responsive applications. By dividing tasks into smaller threads of execution, you can make the most of your system’s resources and create applications that can handle multiple tasks simultaneously. In this blog, we will explore the intricacies of Java multithreading, covering topics such as the life cycle of a thread, thread creation, scheduling, thread priority, and more.
| Sn.No. | Topic |
|--------|-----------------------------|
| 1 | Multithreading |
| 2 | Life Cycle of a Thread |
| 3 | How to Create Thread |
| 4 | Thread Scheduler |
| 5 | Sleeping a thread |
| 6 | Start a thread twice |
| 7 | Calling run() method |
| 8 | Joining a thread |
| 9 | Naming a thread |
| 10 | Thread Priority |
| 11 | Daemon Thread |
| 12 | Thread Pool |
| 13 | Thread Group |
| 14 | ShutdownHook |
| 15 | Performing multiple tasks |
What is Multithreading?
Multithreading is the ability of a CPU or a single core in a multi-core processor to provide multiple threads of execution concurrently. Threads are the smallest unit of execution within a process, and in Java, they are instances of the Thread
class. Multithreading is beneficial in applications where you want to perform multiple tasks simultaneously, such as running background processes, handling user interactions, or managing I/O operations.
Life cycle of a Thread (Thread States)
Thread goes through several states during its lifecycle. Understanding the different thread states is crucial for effective multithreaded programming. The possible thread states are as follows:
- New: A thread is in the “New” state when it’s created but has not yet been started using the
start()
method. In this state, the thread is not eligible for execution. - Runnable: A thread is in the “Runnable” state when it’s ready to run, but the thread scheduler hasn’t selected it to run on the CPU yet. It’s waiting for its turn to be executed. The thread could be in the “Runnable” state because it’s competing with other threads for CPU time.
- Blocked: A thread enters the “Blocked” state when it’s temporarily inactive because it’s waiting for a monitor lock. This happens when a thread attempts to enter a
synchronized
block or method that another thread is currently executing. The blocked thread will stay in this state until it can obtain the lock. - Waiting: A thread enters the “Waiting” state when it’s waiting indefinitely for another thread to perform a specific action. This can occur when a thread invokes the
Object.wait()
method, theThread.join()
method with no timeout, or enters aLockSupport.park()
call. The thread will remain in this state until another thread notifies it to wake up or a specific timeout occurs. - Timed Waiting: A thread is in the “Timed Waiting” state when it’s waiting for a specified period. This state can be caused by methods like
Thread.sleep()
,Object.wait()
with a timeout, andThread.join()
with a timeout. The thread will exit this state either when the timeout expires or when it's interrupted. - Terminated: A thread enters the “Terminated” state when it has completed its execution or when an unhandled exception terminates it. Once a thread is in the “Terminated” state, it cannot be restarted. You can check if a thread is terminated by calling
isAlive()
, which returnsfalse
.
Here’s a typical progression of a thread’s lifecycle:
- New: A thread is created using a
Thread
object or a class that extends theThread
class. - Runnable: The thread is started by calling the
start()
method. It moves to the "Runnable" state and waits for its turn to execute. - Running: The thread scheduler selects the thread from the “Runnable” state and executes its
run()
method. It is now in the "Running" state. - Blocked, Waiting, or Timed Waiting: The thread may transition to one of these states if it encounters a
synchronized
block, callswait()
,join()
, or other methods that involve waiting. - Terminated: The thread enters the “Terminated” state when its
run()
method completes or if an unhandled exception occurs.
How to Create Thread
There are two main ways to create a thread: by extending the Thread
class or by implementing the Runnable
interface. Here's how to create a thread using both methods:
1. Extending the Thread
Class:
To create a thread by extending the Thread
class, you need to do the following:
- Create a class that extends the
Thread
class. - Override the
run()
method within your class. This method will contain the code that the thread will execute. - Create an instance of your custom thread class.
- Call the
start()
method on the thread instance to initiate its execution.
Here’s an example:
class MyThread extends Thread {
public void run() {
// Your code here
for (int i = 1; i <= 10; i++) {
System.out.println("Thread: " + i);
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // Start the thread
}
}
2. Implementing the Runnable
Interface:
Creating a thread by implementing the Runnable
interface is often a more flexible approach, as it allows your class to extend another class and implement multiple interfaces. Here's how to do it:
- Create a class that implements the
Runnable
interface and provides an implementation for therun()
method. - Create an instance of your custom class that implements
Runnable
. - Pass this instance to a
Thread
object. - Call the
start()
method on the thread instance to initiate its execution.
Here’s an example:
class MyRunnable implements Runnable {
public void run() {
// Your code here
for (int i = 1; i <= 10; i++) {
System.out.println("Thread: " + i);
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // Start the thread
}
}
Both approaches have their advantages. Extending the Thread
class is simpler and might be more appropriate when you want to create a new class with a dedicated thread. Implementing the Runnable
interface is more flexible and allows a class to implement multiple interfaces or extend another class while providing the thread's behavior.
Thread Scheduler
The thread scheduler is a critical component of the operating system and the Java Virtual Machine (JVM) responsible for managing the execution of multiple threads in a concurrent environment. It decides which threads run on the CPU and when they run, effectively controlling the allocation of CPU time to each thread. Let’s explore the thread scheduler in more detail:
Thread Priority:
- Threads in Java have a priority associated with them, ranging from
Thread.MIN_PRIORITY
toThread.MAX_PRIORITY
. Higher-priority threads are given preference by the thread scheduler when determining which thread to execute next. However, the actual behavior can vary between different operating systems and JVM implementations.
Time Slicing:
- To give the appearance of concurrent execution on a single CPU core, the thread scheduler uses a technique called time slicing. Each thread is allocated a small time quantum during which it can execute its code. When this time quantum expires, the scheduler switches to another thread, and the process continues, creating the illusion of concurrent execution.
Thread State Transitions:
- The thread scheduler manages the transitions of threads between various states, including “Runnable,” “Blocked,” “Waiting,” and “Timed Waiting.” Threads transition between these states based on their execution context and synchronization mechanisms like
synchronized
blocks andwait()
/notify()
.
Fairness and Synchronization:
- The thread scheduler strives to be fair in allocating CPU time among threads. However, it cannot guarantee absolute fairness, and the actual scheduling behavior may depend on the specific operating system and JVM. Synchronization mechanisms, such as
synchronized
blocks andwait()
/notify()
, can affect scheduling decisions.
Preemptive and Non-Preemptive Schedulers:
- The thread scheduler can be either preemptive or non-preemptive, depending on the operating system and JVM. A preemptive scheduler can interrupt a running thread to switch to another thread, while a non-preemptive scheduler only switches threads at well-defined points, such as when a thread voluntarily yields the CPU or blocks.
Thread Prioritization:
- You can set the priority of a thread in Java using the
setPriority()
method. However, the actual impact of thread priorities can be limited in some scenarios. Thread priorities are used as hints to the scheduler, but they may not have a significant influence on the order of execution, especially on modern multi-core processors.
Thread Yielding:
- Threads can explicitly yield the CPU using the
Thread.yield()
method. This is a hint to the scheduler that the current thread is willing to let other threads run. However, the scheduler may or may not honor this hint, depending on its own policies.
Thread Interruption:
- Thread scheduling can also be influenced by thread interruption using the
interrupt()
method. When a thread is interrupted, it can check its own interrupt status and respond accordingly to terminate gracefully.
The specifics of how the thread scheduler operates can vary between different operating systems and JVM implementations. When writing multithreaded applications, it’s crucial to design your code to be thread-safe and not rely too heavily on fine-grained control over thread scheduling, as it can lead to platform-dependent behavior.
Sleeping a thread
you can make a thread “sleep” for a specified amount of time using the Thread.sleep()
method. This method is used to pause the execution of a thread for a specified duration. Here's how to use it:
try {
// Sleep for 2 seconds (2000 milliseconds)
Thread.sleep(2000);
} catch (InterruptedException e) {
// Handle the exception if the thread is interrupted while sleeping
}
Here’s what happens when you use Thread.sleep()
:
- You specify the sleep duration in milliseconds as an argument to
Thread.sleep()
. In the example above, the thread sleeps for 2000 milliseconds, which is 2 seconds. - The
Thread.sleep()
method can throw anInterruptedException
. This is because other threads can interrupt the sleeping thread using theinterrupt()
method. It's a way to wake the thread up before its sleep duration is complete. You should catch this exception and handle it appropriately.
Here’s an example of using Thread.sleep()
in the context of a simple Java program:
public class SleepExample {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
System.out.println("Task " + i + " is starting.");
try {
// Sleep for 1 second (1000 milliseconds)
Thread.sleep(1000);
} catch (InterruptedException e) {
// Handle the exception if the thread is interrupted while sleeping
}
System.out.println("Task " + i + " is completed.");
}
}
}
In this example, the program performs a task, sleeps for 1 second, and then proceeds to the next task. The Thread.sleep()
method is used to introduce a delay between tasks.
Keep in mind that Thread.sleep()
is a static method and can be called from any context. It's essential to handle the InterruptedException
in case the thread is interrupted while sleeping to avoid unexpected behavior.
Start a thread twice
Example step by step and explain it in detail.
public class StartMultipleThreadsExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable("Thread 1"));
Thread thread2 = new Thread(new MyRunnable("Thread 2"));
thread1.start(); // Start the first thread
thread2 start(); // Start the second thread
}
}
In this example:
- We create a class named
StartMultipleThreadsExample
with amain
method as the entry point for the program. - Inside the
main
method, we create two instances of theThread
class:thread1
andthread2
. These threads will run concurrently, and we provide a name for each thread to identify them. TheMyRunnable
class is used as theRunnable
target for these threads. - The
MyRunnable
class implements theRunnable
interface. It has a constructor that takes aString
parameter, which is used to set the name of the thread.
class MyRunnable implements Runnable {
private String threadName;
public MyRunnable(String name) {
this.threadName = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " is running, count: " + i);
}
}
}
In the MyRunnable
class:
- We store the name of the thread in the
threadName
variable via the constructor. - We override the
run
method from theRunnable
interface. This method contains the code that will be executed when the thread is started. - Inside the
run
method, we run a loop from 1 to 5, printing the thread name and the loop count to the console. This simulates the work the thread is doing.
In the main
method:
- We create two separate instances of the
Thread
class,thread1
andthread2
, each associated with a uniqueMyRunnable
instance. - We start both threads using the
start()
method. This initiates their execution.
Calling run() method
you can call the run()
method directly, but doing so does not start a new thread of execution like calling start()
on a Thread
object. When you call the run()
method directly, it runs in the context of the current thread, as a regular method call. This is not the intended way to create a new thread of execution.
Here’s an example to illustrate calling the run()
method directly:
public class CallRunMethodExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
// Calling run() directly as a regular method call
myRunnable.run();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable is running in thread: " + Thread.currentThread().getName());
}
}
In this example, the run()
method of the MyRunnable
class is called directly, but it's executed in the context of the main thread, not in a separate thread.
Joining a thread
The join()
method is used to wait for a thread to complete its execution before continuing with the current thread. This method is often used to synchronize the execution of multiple threads, ensuring that one thread finishes its work before another one proceeds. Here's how you can use the join()
method:
public class JoinExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable("Thread 1"));
Thread thread2 = new Thread(new MyRunnable("Thread 2"));
// Start both threads
thread1.start();
thread2.start();
try {
// Wait for thread1 to complete
thread1.join();
} catch (InterruptedException e) {
System.out.println("Thread 1 was interrupted.");
}
System.out.println("Thread 1 has finished.");
try {
// Wait for thread2 to complete, with a maximum wait time of 2000 milliseconds (2 seconds)
thread2.join(2000);
} catch (InterruptedException e) {
System.out.println("Thread 2 was interrupted.");
}
System.out.println("Thread 2 has finished.");
}
}
class MyRunnable implements Runnable {
private String threadName;
public MyRunnable(String name) {
this.threadName = name;
}
@Override
public void run() {
System.out.println(threadName + " is running.");
try {
// Simulate some work
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(threadName + " was interrupted.");
}
}
}
In this example:
- We create two threads,
thread1
andthread2
, both associated with aMyRunnable
instance. These threads will run concurrently. - We start both threads using the
start()
method. - We use the
join()
method to wait forthread1
to complete before moving on with the main thread. This ensures that "Thread 1 has finished" is printed afterthread1
completes its execution. - We use the
join(long millis)
method to wait forthread2
to complete but with a maximum wait time of 2000 milliseconds (2 seconds). This allows the program to continue even ifthread2
has not finished within the specified time.
Naming a thread
You can name a thread to make it more identifiable and easier to debug. Naming threads can be particularly helpful when dealing with multiple threads in your application. To name a thread, you can use the setName()
method provided by the Thread
class. Here's how to name a thread:
public class ThreadNamingExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
thread1.setName("WorkerThread-1");
thread1.start();
Thread thread2 = new Thread(new MyRunnable());
thread2.setName("WorkerThread-2");
thread2.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("Thread with name " + threadName + " is running.");
}
}
In this example:
- We create two threads,
thread1
andthread2
, each associated with theMyRunnable
class that implements theRunnable
interface. - We set the names of the threads using the
setName()
method. This makes it easier to identify each thread, especially when debugging or analyzing log output. - We start both threads using the
start()
method. - Inside the
run()
method ofMyRunnable
, we obtain the name of the current thread usingThread.currentThread().getName()
and print it. This allows us to see the thread's name when it's running.
Thread Priority
Thread priority is used to influence the scheduling order of threads by the thread scheduler. Each thread is assigned a priority, which is an integer value that indicates the thread’s importance or urgency. Thread priority values range from Thread.MIN_PRIORITY
(1) to Thread.MAX_PRIORITY
(10), with Thread.NORM_PRIORITY
(5) as the default priority.
Thread priority is a hint to the thread scheduler, and the actual behavior may vary depending on the operating system and JVM implementation. Higher-priority threads are given preference by the scheduler when deciding which thread to execute next. However, the degree to which priority affects scheduling can differ between platforms.
You can set the priority of a thread using the setPriority()
method provided by the Thread
class. Here's how you can set and retrieve thread priorities:
public class ThreadPriorityExample {
public static void main(String[] args) {
Thread highPriorityThread = new Thread(new MyRunnable());
Thread lowPriorityThread = new Thread(new MyRunnable());
// Set the thread priorities
highPriorityThread.setPriority(Thread.MAX_PRIORITY); // Priority 10
lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // Priority 1
// Start the threads
highPriorityThread.start();
lowPriorityThread.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
int threadPriority = Thread.currentThread().getPriority();
System.out.println(threadName + " is running with priority " + threadPriority);
}
}
In this example:
- We create two threads,
highPriorityThread
andlowPriorityThread
, both associated with theMyRunnable
class that implements theRunnable
interface. - We set the priority of
highPriorityThread
to the maximum priority usingThread.MAX_PRIORITY
. We set the priority oflowPriorityThread
to the minimum priority usingThread.MIN_PRIORITY
. - We start both threads using the
start()
method. - Inside the
run()
method ofMyRunnable
, we obtain the name and priority of the current thread and print them.
Daemon Thread
Daemon threads are a type of thread that runs in the background and provides services to user threads. They are typically used for tasks that don’t need to be explicitly completed before the program exits. Daemon threads are automatically terminated when all non-daemon threads have finished executing.
Here are some key characteristics of daemon threads:
- Termination with Program Exit: Daemon threads are automatically terminated when the program exits, regardless of whether they have completed their work or not. Non-daemon threads, on the other hand, must finish their work before the program can exit.
- Service Tasks: Daemon threads are often used for providing services to other threads. For example, a garbage collector or an automatic data-saving mechanism can run as daemon threads in the background.
- setDaemon() Method: You can mark a thread as a daemon thread using the
setDaemon(true)
method on theThread
object before starting it. By default, threads are non-daemon.
Here’s an example of creating a daemon thread:
public class DaemonThreadExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(new DaemonRunnable());
daemonThread.setDaemon(true); // Mark the thread as a daemon
daemonThread.start();
Thread nonDaemonThread = new Thread(new NonDaemonRunnable());
nonDaemonThread.start();
System.out.println("Main thread is exiting.");
}
}
class DaemonRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("Daemon thread is running.");
}
}
}
class NonDaemonRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println("Non-daemon thread is running, iteration: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
In this example:
- We create a
daemonThread
and mark it as a daemon usingsetDaemon(true)
. - We create a
nonDaemonThread
(which is a regular, non-daemon thread). - Both threads are started.
- The main thread prints a message and exits.
Since the daemonThread
is a daemon, it will automatically be terminated when the main thread exits, even if it's running an infinite loop. The nonDaemonThread
will run for a specified number of iterations and then exit.
Daemon threads are useful for background tasks and services that should not prevent the program from exiting when their work is incomplete
Thread Pool
A thread pool is a managed collection of threads that are ready to execute tasks. Thread pools are used to efficiently manage and reuse threads in multithreaded applications, which can improve performance, reduce the overhead of creating and destroying threads, and help control the number of threads running simultaneously. They are commonly used in situations where you need to parallelize work across multiple threads, such as handling concurrent requests in a web server.
Here are the key components and concepts related to thread pools:
- Thread Pool Size: The thread pool has a predefined number of worker threads available for executing tasks. The pool size is typically configured when the thread pool is created.
- Task Queue: Pending tasks are added to a queue. When a thread becomes available, it takes a task from the queue and executes it. This ensures that tasks are processed in an orderly manner without overwhelming the system with too many threads.
- Worker Threads: These are the threads in the thread pool that execute the tasks. The number of worker threads is determined by the pool size.
- Task Submission: You can submit tasks to the thread pool for execution. The thread pool manages the scheduling and execution of these tasks.
- Task Completion: When a task is completed, the worker thread becomes available to pick up another task.
- Thread Pool Implementation: Java provides the
Executor
framework, which includes various interfaces and classes for creating and managing thread pools. TheExecutorService
interface, which extendsExecutor
, is commonly used for managing thread pools.
Here’s an example of creating and using a thread pool in Java:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// Create a thread pool with 2 worker threads
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// Submit tasks to the thread pool
threadPool.submit(new Task("Task 1"));
threadPool.submit(new Task("Task 2"));
threadPool.submit(new Task("Task 3"));
threadPool.submit(new Task("Task 4"));
// Shutdown the thread pool when done
threadPool.shutdown();
}
}
class Task implements Runnable {
private String taskName;
public Task(String name) {
this.taskName = name;
}
@Override
public void run() {
System.out.println(taskName + " is executing by Thread: " + Thread.currentThread().getName());
}
In this example, we create a thread pool with two worker threads using Executors.newFixedThreadPool(2)
. We then submit four tasks to the thread pool. The thread pool manages the execution of these tasks using the available worker threads. When all tasks are completed, we shut down the thread pool.
Thread pools are beneficial in scenarios where you need to parallelize work efficiently, manage thread resources, and control the number of concurrent threads in your application. They are widely used in various multithreaded applications, including web servers, application servers, and batch processing systems.
Thread Group
A thread group in Java is a way to group multiple threads together for management and common control. Thread groups provide a way to organize and categorize threads, making it easier to perform operations on all the threads within a group. However, it’s important to note that thread groups are not commonly used in modern Java programming due to their limitations and complexity.
Here are some key points about thread groups:
- Hierarchy: Thread groups can be organized into a hierarchy, forming a tree-like structure with a top-level thread group. This hierarchical structure is useful for managing threads with different roles or functions within an application.
- Common Operations: Thread groups allow you to perform operations on multiple threads at once. For example, you can interrupt or suspend all threads in a group collectively.
- Delegation of Uncaught Exceptions: You can set a thread group to handle uncaught exceptions for its threads. This means that any uncaught exception in a thread within the group is passed to the thread group for handling.
- Security and Access Control: Thread groups can be used to manage access control and security within a Java application. You can set permissions for thread groups to restrict or allow certain actions for threads within the group.
- Thread Group Limits: Java provides some basic limitations and methods for thread group management. For example, you can create a thread group, add threads to it, and set its parent group. However, the usefulness of thread groups is limited, and they are not commonly used in modern Java development.
Here’s a basic example of how to create and use a thread group:
public class ThreadGroupExample {
public static void main(String[] args) {
ThreadGroup myGroup = new ThreadGroup("MyThreadGroup");
Thread thread1 = new Thread(myGroup, new MyRunnable(), "Thread 1");
Thread thread2 = new Thread(myGroup, new MyRunnable(), "Thread 2");
thread1.start();
thread2.start();
System.out.println("Active threads in " + myGroup.getName() + ": " + myGroup.activeCount());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
In this example:
- We create a
ThreadGroup
named "MyThreadGroup." - We create two threads,
thread1
andthread2
, and assign them to the "MyThreadGroup." - Both threads are started, and they execute the
run
method, which prints their names. - We query the number of active threads in the “MyThreadGroup” using the
activeCount
method.
As mentioned earlier, thread groups are not commonly used in modern Java applications. They have certain limitations, and better alternatives for managing and organizing threads are available, such as using the Executor framework for thread management and task execution.
ShutdownHook
A shutdown hook is a way to register a piece of code that will be executed when the Java Virtual Machine (JVM) is about to shut down or exit. Shutdown hooks are useful for performing cleanup operations, releasing resources, or saving application state before the JVM terminates. They allow you to gracefully handle the shutdown process.
You can register a shutdown hook using the Runtime
class's addShutdownHook(Thread hook)
method or, in more modern Java applications, by implementing the java.lang.AutoCloseable
interface or by using try-with-resources
constructs. The registered code is executed when one of the following events occurs:
- When the program terminates normally (e.g., by calling
System.exit()
or when themain
method finishes executing). - When the JVM receives a termination signal (e.g., via
Ctrl+C
from the command line).
Here’s an example of how to use a shutdown hook to perform cleanup when the JVM is about to exit:
public class ShutdownHookExample {
public static void main(String[] args) {
// Register a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown hook is running. Performing cleanup.");
// Add your cleanup logic here, such as closing resources or saving state.
}));
// Simulate some work
for (int i = 1; i <= 5; i++) {
System.out.println("Working... " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
In this example:
- We register a shutdown hook using
Runtime.getRuntime().addShutdownHook(new Thread(...))
. The code inside the lambda expression will be executed when the program exits. - We simulate some work within the
main
method, and the program prints messages. - If you forcefully terminate the program (e.g., by pressing
Ctrl+C
), the shutdown hook is executed, and the cleanup code is run.
Shutdown hooks are particularly useful for tasks such as releasing resources (e.g., closing files or network connections), saving application state, or logging exit status. They help ensure that your application gracefully shuts down even in unexpected circumstances.
Performing multiple task
Performing multiple tasks concurrently or in parallel using threads is a common use case in multithreaded programming. You can create multiple threads and assign each thread a specific task or job to be executed concurrently. Here’s how to perform multiple tasks in parallel using threads in Java:
public class MultipleTaskExample {
public static void main(String[] args) {
// Create multiple threads for different tasks
Thread task1Thread = new Thread(new Task1());
Thread task2Thread = new Thread(new Task2());
// Start the threads to perform the tasks concurrently
task1Thread.start();
task2Thread.start();
// Main thread continues to do other work or can wait for threads to finish
}
}
class Task1 implements Runnable {
@Override
public void run() {
// Define the task to be performed by thread 1
System.out.println("Task 1 is being executed.");
}
}
class Task2 implements Runnable {
@Override
public void run() {
// Define the task to be performed by thread 2
System.out.println("Task 2 is being executed.");
}
}
In this example:
- We create two separate threads,
task1Thread
andtask2Thread
, each associated with a specific task defined in theTask1
andTask2
classes, respectively. - We start both threads using the
start()
method. This initiates the execution of the tasks concurrently. - The main thread can continue to perform other work or wait for the threads to complete, depending on the requirements of your application.
By creating multiple threads and assigning each thread a specific task to execute, you can achieve concurrent execution of these tasks. This allows you to utilize multiple CPU cores and improve the overall performance of your application when dealing with tasks that can be executed in parallel.