In Java, multitasking is achieved using threads. To write code that runs concurrently on a separate CPU core, you must define the execution payload. Java offers two primary ways to create threads: **extending the Thread class** or **implementing the Runnable interface**.

Thread Class vs Runnable Interface Visual
Real-World Analogy: Hiring a Dedicated Delivery Driver vs. Writing a Task List

Imagine you run a pizza shop and need to make deliveries:

  • Extending Thread: You hire a dedicated **Pizza Delivery Driver (MyThread)** who has a built-in delivery cycle. This person does nothing but deliver pizza. This is simple, but now you cannot hire them to clean the kitchen because their entire identity is tied to being a driver.
  • Implementing Runnable: You write down a **recipe checklist index card (MyRunnable)** containing the steps for making a delivery. You can hand this card to any employee you already have (the generic Thread wrapper) or throw it into a kitchen team tasks bin (a Thread Pool). This is highly flexible!

Extending Thread vs. Implementing Runnable

Feature Extending Thread Implementing Runnable
Inheritance Limit Multiple inheritance blocked (Java classes can only extend one class). Can extend any parent class while implementing Runnable.
Decoupling Task logic and execution management are tightly coupled. Decoupled: Task logic can be passed to Executor pools easily.
Object Sharing Each thread owns its instance variables. Multiple threads can share a single Runnable instance.

Java Implementation

package io.practise.threadsExample;
 
public class ThreadExample {
    public ThreadExample() {
        // Approach 1: Extending Thread
        new SimpleThread("Thread 1").start();
        new SimpleThread("Thread 2").start();
 
        // Approach 2: Implementing Runnable
        Thread thread = new Thread(new SecondSimpleThread());
        thread.start();
    }
 
    public static void main(String args[]) {
        new ThreadExample();
    }
}
 
// Approach 1: Extend Thread class
class SimpleThread extends Thread {
    public SimpleThread(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getName() + " " + i);
        }
    }
}
 
// Approach 2: Implement Runnable interface
class SecondSimpleThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getClass().getSimpleName() + " " + i);
        }
    }
}

Conclusion

In modern production environments, implementing `Runnable` (or `Callable`) is the recommended best practice. It maintains class inheritance flexibility and allows you to submit tasks directly to concurrency frameworks like ExecutorService.