在 Java 中,继承 Thread 类和实现 Runnable 接口是两种经典的多线程实现方式。它们本质上都是通过 Thread 类来执行线程,但设计理念和适用场景有明显区别。
核心区别总结
| 对比维度 | 继承 Thread 类 |
实现 Runnable 接口 |
|---|---|---|
| 资源共享 | 较难实现(需要共享静态变量或外部对象) | 天然支持多个线程共享同一个 Runnable 实例 |
| 灵活性 | 低(Java单继承限制) | 高(可以继承其他类) |
| 代码与逻辑分离 | 未分离(线程创建和任务代码耦合) | 已分离(线程和任务解耦) |
| 线程池支持 | 不推荐使用(Thread 对象不能被重用) |
推荐使用(Runnable 或 Callable 提交给线程池) |
| 适用场景 | 简单、临时、不需要共享数据的线程 | 复杂、需要共享资源、或已有父类的场景 |
详细对比与示例
1. 资源共享能力
继承 Thread 类:每个线程对象拥有自己独立的成员变量副本,要共享数据通常需要 static 变量或传递公共对象。
// 继承Thread - 不共享数据(三个线程各卖10张票)
class TicketThread extends Thread {
private int tickets = 10;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出一张,剩余:" + --tickets);
}
}
}
// 主方法中:三个线程各自独立卖出10张票
new TicketThread().start();
new TicketThread().start();
new TicketThread().start();
实现 Runnable 接口:天然支持共享数据。
// 实现Runnable - 共享数据(三个线程一起卖10张票)
class TicketRunnable implements Runnable {
private int tickets = 10;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出一张,剩余:" + --tickets);
}
}
}
// 主方法中:三个线程共享同一个Runnable实例
TicketRunnable task = new TicketRunnable();
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
2. 继承限制
- 继承
Thread:占用唯一的extends机会,无法再继承其他类。 - 实现
Runnable:可以继续继承其他类或实现其他接口,更灵活。
3. 设计原则
- 继承
Thread:违反“组合优于继承”原则,且将“线程”和“任务”强耦合。 - 实现
Runnable:符合单一职责原则,Runnable只负责任务内容,Thread负责线程调度和资源管理。
4. 代码重用与扩展
- 继承
Thread:任务代码内嵌在子类中,不易替换或组合不同任务。 - 实现
Runnable:任务实例可以传递给多个Thread,也可以提交给ExecutorService,支持线程池、定时调度等高级并发工具。
现代 Java 推荐做法
官方建议:优先使用
Runnable(或Callable)而不是继承Thread。
即使你继承 Thread,内部通常也会建议重写 run(),但本质上还是推荐将任务实现为 Runnable。在 JDK 1.8+ 中,甚至可以直接使用 Lambda 表达式简化:
// Lambda方式(等效于Runnable)
Thread t = new Thread(() -> {
System.out.println("任务执行中...");
});
t.start();
总结
| 选择原则 | 推荐方式 |
|---|---|
| 需要共享数据(如卖票、计数器) | Runnable |
类已经有父类,无法继承 Thread |
Runnable |
| 需要将任务提交给线程池 | Runnable / Callable |
| 只是临时简单测试,不需要共享数据 | 两者均可,更推荐 Runnable |
本质认知:Thread 是一个“工人”(执行器),Runnable 是“工作内容”(任务)。应该把任务和工人分开,这样更灵活、更符合面向对象设计。
