Difference Between Thread and Runnable in Java

In Java, creating new threads for concurrent execution involves two main approaches: extending the Thread class or implementing the Runnable interface. Both achieve similar goals, but with key differences that impact code flexibility and design. Here's a breakdown of each approach with examples:

Here's a table summarizing the key points:

Feature
Extends Thread
Implements Runnable
Inheritance
Inherits from Thread
Implements interface
Multiple Inheritance
Not Allowed
Allowed
Reusability
Less flexible
More flexible
Code Separation
Less separation
Clear separation
Recommended Use Case
Simple scenarios
Most scenarios

Key Differences and When to Choose Which:

Extending Thread

  1. Inheritance: When you extend Thread, your class inherits all its methods and properties. You can override the run() method to define the code the thread will execute.
  2. Limited Inheritance: Java supports single inheritance, meaning a class can only extend one other class. If your class needs to inherit functionalities from another class besides Thread, this approach becomes restrictive.
Example:
public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread extending Thread: " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }

Implementing Runnable

  1. Interface: The Runnable interface defines a single method, run(), which contains the code the thread will execute. Your class implements this interface and provides the implementation for run().
  2. Flexible Inheritance: Unlike extending Thread, you can still extend another class while implementing Runnable. This allows for more versatile class design and code reuse.
  3. Separate Concerns: Implementing Runnable focuses on the task the thread performs, while creating a Thread object handles thread management aspects like scheduling and lifecycle. This separation promotes cleaner design.
Example:
public class MyTask implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread implementing Runnable: " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyTask task = new MyTask(); Thread thread = new Thread(task); thread.start(); } }

Extends Thread Vs Implements Runnable | Java

Inheritance

When you extend Thread, your class cannot extend any other class because Java doesn't support multiple inheritance. However, when you implement Runnable, your class can still extend other classes or implement other interfaces.

Design

Implementing Runnable separates the thread's behavior from the thread's execution. This is generally considered a cleaner approach, as it follows the composition over inheritance principle.

Flexibility

Implementing Runnable gives you more flexibility, as you can pass the same Runnable instance to multiple threads, whereas if you extend Thread, each new thread requires a new instance of your class.

Resource Management

Implementing Runnable allows you to separate the thread's behavior from the thread's object. This can be useful in resource-constrained environments, as creating a new thread object is relatively expensive.

Example:

Suppose you have a scenario where you need to run a task of printing numbers in two different ways. Using both approaches:

// Extending Thread class NumberPrinter extends Thread { private int start; private int end; public NumberPrinter(int start, int end) { this.start = start; this.end = end; } public void run() { for (int i = start; i <= end; i++) { System.out.println(i); } } } // Implementing Runnable class NumberPrinterRunnable implements Runnable { private int start; private int end; public NumberPrinterRunnable(int start, int end) { this.start = start; this.end = end; } public void run() { for (int i = start; i <= end; i++) { System.out.println(i); } } } public class Main { public static void main(String[] args) { // Using Thread subclass NumberPrinter printer1 = new NumberPrinter(1, 5); printer1.start(); // Using Runnable NumberPrinterRunnable printerRunnable = new NumberPrinterRunnable(6, 10); Thread printer2 = new Thread(printerRunnable); printer2.start(); } }

Conclusion

Implementing Runnable is the preferred approach due to its greater flexibility and better separation of concerns. It allows you to create reusable tasks and leverage inheritance if needed. Extending Thread might be suitable for very specific, simple use cases where inheritance isn't a concern.