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**.
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.