What is multithreading in Java?

Multithreading in Java refers to concurrently executing two or more threads (lightweight subprocesses) within a single program. This feature allows Java applications to perform multiple tasks simultaneously, improving efficiency and performance.

The primary purpose of multithreading is to maximize CPU utilization and enhance application responsiveness, especially in scenarios where tasks are independent and can be executed in parallel. Suppose a web server handles multiple client requests. Each client request can be processed by a separate thread, enabling the server to serve multiple clients concurrently without significant delays.

So the question in our mind might come, what is multitasking and threads? Let's explore first this concept in-depth below:

What is multitasking?

Multitasking refers to the ability of an operating system to execute multiple tasks (processes or threads) simultaneously. Multitasking can be categorized into two types:

1. Process-based multitasking

  • In process-based multitasking, multiple processes run concurrently. Each process has its own memory space. Thises process-based multitasking, is used for executing independent programs, such as running a browser and a word processor simultaneously.

  • For example: Watching a video on a media player while downloading files using a download manager.

2. Thread-based multitasking

  • In thread-based multitasking, multiple threads of a single process execute concurrently. Threads share the same memory space, making this approach more efficient for tasks that require interaction or shared resources.

  • For example: In a word processor, one thread can check spelling while another saves the document in the background.

So the question again arises what is thread? Let's discuss threads in Java:

What is a thread in Java?

A thread in Java is the smallest unit of a program that can execute independently. Threads in Java are like small, independent tasks within a program. You can create and control them using the Thread class or the Runnable interface. Threads allow for parallel execution of tasks, making programs more responsive and efficient.

For example: In a gaming application, a thread can handle rendering graphics while another manages user input.

Now that we understand what threads are, let’s explore their lifecycle to see how they function in a program.

Lifecycle of a thread in Java

The lifecycle of a thread in Java consists of the following states:

1. New
  • Definition: A thread is created using the Thread class but has not yet started.

  • Syntax: Thread t = new Thread();

  • Purpose: Prepares the thread for execution.

2. Runnable
  • Definition: A thread is ready to run and waiting for CPU time.

  • Syntax: t.start();

  • Purpose: Marks the thread as eligible for execution.

3. Running
  • Definition: The thread is actively executing its task.

  • Purpose: The thread is assigned CPU time for execution.

Lifecycle of a thread in Java
Lifecycle of a thread in Java
4. Blocked/Waiting
  • Definition: The thread is temporarily paused, waiting for a resource or signal.

  • Purpose: Ensures resources are allocated safely without conflicts.

5. Terminated
  • Definition: The thread completes its task or is manually stopped.

  • Syntax: Thread execution ends naturally or using t.stop();

  • Purpose: Frees up system resources.

What is multithreading in Java?

Multithreading in Java is a technique that allows multiple threads to run concurrently within a single program. Multithreading enhances performance and responsiveness by executing independent tasks simultaneously. For example, A video streaming application where one thread handles buffering, another manages user input, and another updates the interface.

Methods of multithreading in Java

Java provides two primary ways to create and manage threads:

1. Using the thread class
  • How it works: Extend the Thread class and override its run() method.

  • Code example:

class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
public class main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
2. Using the runnable interface
  • How it works: Implement the Runnable interface and pass it to a Thread object.

  • Code example:

class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread is running");
}
}
public class main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}

Example of multithreading in Java

Let's discuss the example of multithreading where we will show how to create two tasks that run concurrently. Task1 extends the Thread class, and Task2 implements the Runnable interface. Both tasks print their respective messages in a loop. These threads are started using the start() method to execute simultaneously.

Here is a practical example demonstrating multithreading:

// Task1 class extends the Thread class to create a thread
class Task1 extends Thread {
// The run() method defines the task this thread will execute
public void run() {
// Loop to print Task1 messages five times
for (int i = 0; i < 5; i++) {
System.out.println("Task 1 - " + i); // Print the current iteration for Task1
}
}
}
// Task2 class implements Runnable to define a thread's task
class Task2 implements Runnable {
// The run() method defines the task for this thread
public void run() {
// Loop to print Task2 messages five times
for (int i = 0; i < 5; i++) {
System.out.println("Task 2 - " + i); // Print the current iteration for Task2
}
}
}
// Main class to test the threads
public class main {
public static void main(String[] args) {
// Create an instance of Task1, which extends Thread
Task1 t1 = new Task1();
// Create an instance of Task2 and pass it to a Thread object
Thread t2 = new Thread(new Task2());
// Start the first thread (Task1)
t1.start();
// Start the second thread (Task2)
t2.start();
}
}

Explanation:

Line 2–10: Task1 class extends the Thread class, allowing it to directly represent a thread. public void run() method is overridden to define the task for Task1. The JVM calls this method when the thread starts. A loop iterates five times, printing the current iteration with "Task 1" as a prefix.

Line 13–21: Task2 class implements the Runnable interface, which is another way to define a thread's task. new Thread(new Task2()). A Task2 instance is wrapped in a Thread object since Runnable must be passed to a Thread for execution.

Line 33–36: t1.start() and t2.start() threads are started using the start() method, invoking their run() methods and executing them concurrently.

Modify the code to create a new task class (e.g., Task3) using either Thread or Runnable, and print custom messages alongside Task1 and Task2 to observe concurrent execution.

Multiprocessing vs. multithreading in Java

Multiprocessing

Multithreading

Running multiple processes concurrently.

Running multiple threads concurrently.

Requires more memory (separate memory).

Shares memory within the same process.

Suitable for CPU-intensive tasks.

Lower overhead for context switching.

Higher overhead for context switching.

Lower overhead for context switching.

Key takeaways

  1. Excessive threads can lead to resource contention and overhead due to context switching. Use thread pools to manage threads effectively.

  2. For shared data between threads, prefer thread-safe data structures like ConcurrentHashMap or synchronized blocks to avoid race conditions.

  3. Use synchronization judiciously to avoid thread deadlocks and performance bottlenecks. Only synchronize critical sections of code.

  4. Use Java's ExecutorService and other classes from the java.util.concurrent package for better thread management.

  5. When a thread is interrupted, ensure it gracefully stops or cleans up resources instead of abruptly terminating.

  6. Implement the Runnable interface whenever possible as it allows the class to inherit from another class, improving flexibility.

  7. Reduce thread-blocking calls like Thread.sleep() or I/O operations within critical threads to prevent slowing down the application.

Unlock your Java potential and take control of your coding journey! Start learning Java today to explore other than multithreading, build Java-based applications, and become a Java developer now!

Frequently asked questions

Haven’t found what you were looking for? Contact Us


What is the difference between single thread and multi-thread in Java?

  • A single-thread application executes tasks sequentially, one after another, where only one thread performs operations at a time.
  • A multi-threaded application allows multiple threads to run concurrently, enabling tasks to be executed simultaneously. This improves efficiency and responsiveness, especially for tasks that can operate independently.

Why is Java multithreaded?

Java is multithreaded to enhance performance, responsiveness, and resource utilization. With its inbuilt Thread class and Runnable interface, Java simplifies the creation and management of threads. Multithreading allows developers to:

  1. Execute multiple tasks simultaneously.
  2. Maximize CPU usage by running threads during idle times (e.g., waiting for I/O).
  3. Build responsive applications, such as GUI programs, where background tasks (e.g., loading data) don’t freeze the user interface.

Is multithreading faster than a single thread?

Yes, multithreading can be faster than single-threaded execution, especially for I/O-bound or independent tasks.


Why is Java single-threaded?

Java itself is not single-threaded; it supports multithreading by design. However, certain programs or processes might use a single thread to simplify logic or reduce complexity, especially when:

  1. Tasks are dependent and must execute sequentially.
  2. The overhead of managing multiple threads outweighs the performance benefits (e.g., small programs with limited processing needs).

Free Resources