Here’s a detailed comparison of implements Runnable
vs. extends Thread
in Java, with practical examples and scenarios:
Key Differences
Feature | implements Runnable | extends Thread |
---|---|---|
Inheritance | ✅ Can extend another class. | ❌ Blocks other inheritance (single parent). |
Reusability | ✅ Same task can run in multiple threads. | ❌ Each thread is a unique instance. |
Separation of Concerns | ✅ Separates task logic from thread mechanics. | ❌ Combines task and thread logic. |
Lambda Support | ✅ Works with lambdas/functional interfaces. | ❌ Requires subclassing. |
Thread Pools | ✅ Compatible with ExecutorService . | ❌ Not ideal for modern thread pools. |
Examples
1. Basic Implementation
Using Runnable
(recommended):
public class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Running via Runnable: " + Thread.currentThread().getName());
}
}
// Usage:
Thread t1 = new Thread(new MyTask(), "Thread-1");
t1.start(); // Starts a new thread executing MyTask's run()
Using Thread
Subclass:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running via Thread: " + getName());
}
}
// Usage:
MyThread t2 = new MyThread();
t2.start(); // Starts the thread
2. Sharing a Task Across Multiple Threads
Only possible with Runnable
:
public class CounterTask implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
}
}
}
// Share the same task between two threads:
CounterTask sharedTask = new CounterTask();
Thread worker1 = new Thread(sharedTask, "Worker-1");
Thread worker2 = new Thread(sharedTask, "Worker-2");
worker1.start();
worker2.start();
// Output (example):
// Worker-1: 0
// Worker-2: 1
// Worker-1: 2
// Worker-2: 3
// Worker-1: 4
// Worker-2: 5
3. Using Lambdas with Runnable
Java 8+ allows concise syntax for Runnable
:
// Lambda Runnable
Runnable task = () -> {
System.out.println("Running via lambda: " + Thread.currentThread().getName());
};
// Start thread directly:
new Thread(task, "Lambda-Thread").start();
4. Thread Pools (ExecutorService
)
Only works with Runnable
/Callable
:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit Runnable tasks:
executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2"));
executor.shutdown();
5. Extending Another Class
Only possible with Runnable
:
public class MyApp extends JFrame implements Runnable {
// Extends JFrame (GUI) and implements Runnable
@Override
public void run() {
// Background task for the GUI
System.out.println("GUI task running...");
}
}
// Usage:
MyApp app = new MyApp();
new Thread(app).start();
6. Overriding Thread.start()
(Risky)
Possible but discouraged with Thread
subclass:
public class CustomThread extends Thread {
@Override
public void run() {
System.out.println("CustomThread running");
}
@Override
public void start() { // ❌ Overriding start() is risky
System.out.println("Custom start()");
super.start(); // Must call super.start() to spawn a thread
}
}
// Usage:
CustomThread t = new CustomThread();
t.start(); // Output: "Custom start()" followed by "CustomThread running"
When to Use Which
- Prefer
Runnable
for: - Tasks needing reuse across threads.
- Modern thread pools (
ExecutorService
). - Classes that must extend another superclass.
- Clean separation of task and threading logic.
- Avoid
Thread
Subclass except for: - Trivial, single-use threads (rare).
- Legacy code requiring direct
Thread
method access.
Best Practices
- Favor
Runnable
for flexibility and modern concurrency. - Use thread pools (
ExecutorService
) instead of rawThread
management. - Avoid overriding
Thread.start()
unless you fully understand threading mechanics.
By choosing Runnable
, you write cleaner, more maintainable code that aligns with Java’s concurrency best practices.