【Java多线程】继承Thread类 vs 实现Runnable接口:两种线程创建方式的深度对比

在Java并发编程中,创建线程是入门必学的基础操作。最经典的两种实现方式分别是继承Thread类和实现Runnable接口(再将实例传入Thread构造器)。很多初学者会困惑:这两种方式到底有什么区别?各自的适用场景是什么?本文将从代码实现、设计思想、实际应用等维度,对这两种线程创建方式进行全面拆解。
一、方式一:继承Thread类创建线程
- 实现步骤
- 定义一个类,继承java.lang.Thread类。
- 重写Thread类中的run()方法,该方法体就是线程要执行的核心任务。
- 创建该子类的实例对象,即线程对象。
- 调用线程对象的start()方法,启动线程(注意:start()会自动触发JVM调用run()方法,不能直接调用run())。
- 代码示例
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + “:执行第 " + i + " 次任务”);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建两个独立的线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 为线程命名
thread1.setName("线程A");
thread2.setName("线程B");
// 启动线程
thread1.start();
thread2.start();
}
}
3. 特点分析
• 单继承限制:Java是单继承语言,继承Thread类后,无法再继承其他类,扩展性受限。
• 资源共享困难:每个线程对象都是独立实例,若要实现多线程共享资源(如卖票场景),需依赖静态变量,代码耦合度高。
• 任务与线程绑定:任务逻辑(run())和线程对象(Thread子类)耦合在一起,无法独立复用任务逻辑。
二、方式二:实现Runnable接口创建线程
- 实现步骤
- 定义一个类,实现java.lang.Runnable接口。
- 实现接口中的run()方法,编写线程任务逻辑。
- 创建该实现类的实例对象(即任务对象,而非线程对象)。
- 将任务对象作为参数,传入Thread类的构造器,创建真正的线程对象。
- 调用线程对象的start()方法,启动线程。
- 代码示例
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + “:执行第 " + i + " 次任务”);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 创建一个任务对象(可被多个线程共享)
MyRunnable task = new MyRunnable();
// 将同一个任务对象传入不同的Thread构造器,创建多个线程
Thread thread1 = new Thread(task, "线程A");
Thread thread2 = new Thread(task, "线程B");
// 启动线程
thread1.start();
thread2.start();
}
}
3. 特点分析
• 无单继承限制:实现接口后,类仍可继承其他父类,灵活性更高。
• 天然支持资源共享:同一个任务对象可被多个线程共享,实例变量可直接作为共享资源(如卖票场景的票数)。
• 任务与线程分离:任务逻辑(Runnable实现类)和线程对象(Thread)解耦,任务可独立复用、组合。
• 符合设计原则:遵循“组合优于继承”的面向对象设计思想,降低代码耦合度。
三、核心区别对比
对比维度 继承Thread类 实现Runnable接口
继承限制 单继承限制,无法再继承其他类 无单继承限制,可同时继承其他类、实现多接口
资源共享 需依赖静态变量实现,耦合度高 同一个任务对象可被多线程共享,天然支持资源共享
代码复用 任务与线程绑定,复用性差 任务与线程分离,任务逻辑可独立复用
设计思想 继承式设计,耦合度较高 组合式设计,符合“组合优于继承”原则,低耦合
扩展性 与Thread类绑定,难以扩展到线程池等高级工具 可与Executor框架、Callable+Future完美结合,扩展性强
适用场景 简单、独立的线程任务,无需共享资源 复杂场景、需要资源共享、追求代码复用与灵活性
四、场景实战:卖票系统
- 继承Thread类实现卖票
class TicketThread extends Thread {
// 静态变量实现多线程共享票数(耦合度高)
private static int ticket = 10;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket-- + " 张票");
}
}
}
public class TicketDemo1 {
public static void main(String[] args) {
TicketThread window1 = new TicketThread();
TicketThread window2 = new TicketThread();
window1.setName(“窗口1”);
window2.setName(“窗口2”);
window1.start();
window2.start();
}
}
缺陷:必须用静态变量ticket才能实现共享,否则每个线程会有自己的ticket副本,导致数据不一致。
2. 实现Runnable接口实现卖票
class TicketRunnable implements Runnable {
// 实例变量,多个线程共享同一个实例的ticket
private int ticket = 10;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket-- + " 张票");
}
}
}
public class TicketDemo2 {
public static void main(String[] args) {
// 仅创建一个任务对象,所有窗口共享该任务的票数
TicketRunnable task = new TicketRunnable();
Thread window1 = new Thread(task, “窗口1”);
Thread window2 = new Thread(task, “窗口2”);
window1.start();
window2.start();
}
}
优势:无需静态变量,同一个任务实例天然共享资源,代码更简洁、耦合度更低。
五、总结与最佳实践
- 核心结论
• 继承Thread类:实现简单,但受限于Java单继承,资源共享不便,复用性差,仅适合简单、独立的线程任务场景。
• 实现Runnable接口:更灵活、更推荐的方式,避免了单继承限制,天然支持资源共享,代码复用性高,符合面向对象设计原则,是企业开发中的主流选择。 - 进阶延伸
Java 5之后还提供了Callable接口,它与Runnable类似,但可以返回任务结果和抛出异常,结合Future或ExecutorService使用,功能更强大。而Runnable是Callable的基础,两者都可以与线程池等高级并发工具完美结合。 - 新手避坑
• 启动线程必须调用start()方法,直接调用run()只是普通方法调用,不会启动新线程。
• 线程启动后,由CPU调度执行run()方法,执行顺序不确定,需通过同步机制控制并发安全。
六、结语
通过本文的对比,相信你已经清楚了继承Thread类和实现Runnable接口创建线程的核心区别。在实际开发中,优先选择实现Runnable接口的方式,它能让你的代码更灵活、更易于维护,也为后续使用线程池等高级并发工具打下坚实基础。
更多推荐


所有评论(0)