Java两种创建线程方式:继承Thread vs 实现Runnable 区别详解
前言
在Java并发开发体系中,构建线程主要存在两种经典实现途径:一是编写子类继承 Thread 父类,二是自定义类实现 Runnable 接口并交由 Thread 对象承载执行。很多初学者仅会照搬模板编写代码,却不清楚二者底层设计、适用场景与拓展性的核心差距,本文结合实操案例,从语法、设计、业务场景多角度拆解二者区别。
一、两种实现方式完整代码示例
方案1:继承Thread类生成线程
该方案的设计逻辑为:自定义类直接继承 java.lang.Thread ,重写内部 run() 方法承载线程业务逻辑,实例化自定义类后调用 start() 方法启动线程。
// 自定义线程类,继承Thread
public class ExtendThread extends Thread {
// 重写run,线程运行时执行该方法
@Override
public void run() {
for (int num = 1; num <= 8; num++) {
System.out.println("继承Thread线程,输出数字:" + num);
}
}
public static void main(String[] args) {
// 构建两个独立线程对象
ExtendThread threadOne = new ExtendThread();
ExtendThread threadTwo = new ExtendThread();
// 启动线程
threadOne.start();
threadTwo.start();
}
}
方案2:实现Runnable接口构建线程
该方案将业务任务与线程执行载体拆分,自定义类实现 Runnable 接口并重写 run() 封装业务,再把任务实例传入 Thread 构造方法,借助Thread完成线程创建与调度。
// 业务任务类,仅实现Runnable接口
public class ImplRunnable implements Runnable {
@Override
public void run() {
for (int num = 1; num <= 8; num++) {
System.out.println("实现Runnable线程,输出数字:" + num);
}
}
public static void main(String[] args) {
// 仅创建一份任务实例
ImplRunnable task = new ImplRunnable();
// 多个线程共用同一个任务对象
Thread threadOne = new Thread(task);
Thread threadTwo = new Thread(task);
threadOne.start();
threadTwo.start();
}
}
二、五大核心差异点深度对比
1. 类继承约束不同(最核心区分点)
Java语言严格遵循单类继承机制,一个类仅能拥有一个直接父类:
- 继承 Thread 方案:自定义类已经占用唯一继承名额,后续无法再继承其他业务父类,拓展性被锁死;
- 实现 Runnable 方案:接口支持多实现,类实现Runnable后,依旧可以正常继承其他父类、实现多个业务接口,不会限制类的层级拓展。
2. 多线程资源共享能力天差地别
线程开发高频场景为多个线程操作同一公共资源(车票、库存、计数器等),两种方案表现完全不同:
1. 继承Thread:每次 new 自定义线程类都会生成全新独立对象,每个对象拥有专属成员变量,线程之间无法共用数据;
2. 实现Runnable:仅需实例化一份任务对象,多个Thread传入同一个任务实例,所有线程共享任务内部成员变量,天然适配资源争抢场景。
车票售卖实操案例(Runnable共享资源):
// 车票售卖任务,多窗口共享票数
public class SaleTicketTask implements Runnable {
// 所有线程共享的车票库存
private int ticketCount = 12;
@Override
public void run() {
while (ticketCount > 0) {
System.out.println(Thread.currentThread().getName() + "售出剩余第" + ticketCount + "张车票");
ticketCount--;
}
}
public static void main(String[] args) {
SaleTicketTask ticketTask = new SaleTicketTask();
// 三个窗口共用车票任务
new Thread(ticketTask, "一号售票窗口").start();
new Thread(ticketTask, "二号售票窗口").start();
new Thread(ticketTask, "三号售票窗口").start();
}
}
若采用继承Thread方式编写售票逻辑,每新建一个窗口对象都会生成独立车票库存,无法实现多窗口统一售票。
3. 代码分层与耦合程度不一样
- Thread继承模式:线程载体、业务逻辑全部耦合在同一个类中,线程运行机制和业务代码高度绑定,后期如需更换线程执行方式,需要大规模修改业务代码;
- Runnable实现模式:遵循职责单一设计思想,Runnable只负责编写业务逻辑,Thread仅负责操作系统线程的创建、调度、生命周期管理,二者完全解耦,便于单独维护、替换。
4. 底层执行逻辑细微区分
两种方式最终都会调用底层native方法 start0() 向操作系统申请创建线程,执行入口均为 run() 方法,内部调用逻辑存在区别:
1. 继承Thread:线程本身就是任务载体,执行时直接调用自身重写的 run() ;
2. 实现Runnable:Thread内部持有Runnable成员变量,执行时判断任务不为空,调用 runnable.run() ,本质是线程代理执行外部任务。
5. 代码书写简洁度
- 继承Thread:代码行数更少,无需额外创建Thread对象,对新手入门友好;
- 实现Runnable:需要多一层任务类封装,基础代码量略多,但Java 8 Lambda表达式可大幅简化编写,无需单独定义实现类:
public class LambdaThreadDemo {
public static void main(String[] args) {
// Lambda简化Runnable写法,省去独立类定义
new Thread(() -> {
System.out.println("Lambda简化实现Runnable线程");
}).start();
}
}
三、两种实现方式优缺点汇总
继承Thread子类
优势:代码直观简短,入门学习成本低,直接实例化即可启动线程。
短板:受单继承限制拓展性差、多线程无法共享资源、业务与线程逻辑高度耦合,企业项目极少使用。
实现Runnable接口
优势:突破单继承限制、多线程共享公共资源、代码分层解耦、适配Lambda简化写法,是工业级项目标准方案。
短板:基础写法需要额外搭配Thread对象,初学阶段代码结构稍复杂。
四、实际开发选用规范
1. 业务开发、并发业务(库存、订单、多窗口任务)强制选用Runnable实现方案;
2. 仅做简单Demo演示、无拓展需求的极简测试代码,可临时使用继承Thread方式;
3. Java 8及以上版本,优先使用Lambda表达式简化Runnable代码,减少冗余类文件。
五、补充拓展
除本文两种基础方案外,Java还提供 Callable+FutureTask 实现带返回值的线程,适用于需要获取线程执行结果的场景,底层同样基于Runnable接口拓展而来,本质依旧是任务与线程分离的设计思路,侧面印证Runnable模式的设计优势。
