一、前言

在 Java 多线程开发中,继承 Thread 子类实现 Runnable 接口是最基础的两种线程创建方案,二者底层都依托java.lang.Thread完成操作系统线程调度,但在语法约束、资源共享、扩展性、代码设计上存在本质差异。本文从原理、代码、优缺点、适用场景四个维度严谨对比两种实现方式。

二、两种实现方式代码示例

1. 继承 Thread 类方式

通过自定义类继承Thread,重写run()方法定义线程任务,调用实例start()启动线程。

// 1.继承Thread子类
class MyThread extends Thread{
    // 重写run:线程执行逻辑
    @Override
    public void run() {
        System.out.println("继承Thread方式运行线程:"+Thread.currentThread().getName());
    }
}

public class ThreadDemo{
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
    }
}

关键:每 new 一个MyThread,就创建一个独立的线程对象 + 任务对象

2. 实现 Runnable 接口方式

自定义类实现Runnable接口、重写run()封装任务,将任务实例传入Thread构造器,借助 Thread 对象启动线程。

// 1.实现Runnable(仅封装任务)
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("实现Runnable方式运行:"+Thread.currentThread().getName());
    }
}

public class RunnableDemo{
    public static void main(String[] args) {
        // 同一个任务对象,绑定多个线程
        MyRunnable task = new MyRunnable();
        Thread t1 = new Thread(task,"线程A");
        Thread t2 = new Thread(task,"线程B");
        t1.start();
        t2.start();
    }
}

关键:任务与线程分离,一份任务实例可以被多个 Thread 线程复用。

三、核心区别对比(严谨分维度)

1. 类继承语法限制(最核心差异)

  • 继承 Thread:Java 是单继承机制,子类继承Thread无法再继承其他父类,类的继承链路被占用,扩展性受限。
  • 实现 Runnable:接口是多实现,实现Runnable仍可以继承任意父类、实现多个其他接口,符合 Java 面向接口编程规范。

2. 资源共享能力(多线程共享数据场景)

场景:多个线程操作同一个票数变量。

  • 继承 Thread:每个线程都是独立MyThread实例,成员变量属于实例私有,想要共享资源需要将变量定义为static静态变量(全局唯一),静态变量生命周期过长、容易引发内存泄漏。
class TicketThread extends Thread{
    // 静态变量:所有实例共享票数
    private static int ticket = 10;
    @Override
    public void run() {
        while(ticket>0){
            System.out.println(Thread.currentThread().getName()+"出票:"+ticket--);
        }
    }
}
  • 实现 Runnable任务对象唯一,多个 Thread 共用同一个 Runnable 实例,实例成员变量天然被所有线程共享,无需 static 修饰,资源管控更优雅,是多线程共享数据的首选。
class TicketRunnable implements Runnable{
    // 实例变量:多线程共享,不用static
    private int ticket = 10;
    @Override
    public void run() {
        while(ticket>0){
            System.out.println(Thread.currentThread().getName()+"出票:"+ticket--);
        }
    }
}

3. 代码职责分离设计

  • Thread 继承线程载体(Thread)和业务任务(run)耦合在同一个类,违背单一职责原则,一个类既要负责线程创建调度,又要写业务逻辑,代码臃肿不利于后期维护。
  • Runnable 实现任务和线程解耦,Runnable 只负责封装业务任务,Thread 专门负责线程调度、生命周期管理,符合单一职责,便于任务复用、线程池调度。

4. 底层原理

  1. Thread.start()调用本地方法native start0(),向操作系统申请创建内核线程,操作系统回调run()方法。
  2. 继承 Thread:重写本类run(),直接覆盖父类 Thread 空 run 方法。
  3. 实现 Runnable:Thread 源码中存在成员变量private Runnable target;,传入 Runnable 后,Thread 原生run()会调用target.run(),本质是组合复用优于继承复用。
// Thread源码原生run方法
public void run() {
    if (target != null) {
        target.run();
    }
}

5. 适配线程池

线程池Executors/ThreadPoolExecutor入参统一接收Runnable类型任务,Runnable 实现类可以直接丢入线程池复用;继承 Thread 的线程类无法直接作为线程池任务,必须改造为 Runnable。生产环境线程池是多线程主流方案,因此 Runnable 通用性更强。

四、两种方案优缺点总结

表格

对比项 继承 Thread 类 实现 Runnable 接口
继承限制 单继承,无法再拓展父类 多实现,无继承约束,拓展灵活
资源共享 依赖 static 静态变量共享,弊端多 天然实例共享变量,推荐多线程共用资源
设计原则 任务 + 线程耦合,违背单一职责 任务、线程解耦,面向接口编程
线程池适配 不友好,无法直接入池 完美适配 Java 线程池框架
代码简洁度 少量简单任务代码简短 需要额外创建 Thread 对象,代码略多

五、生产环境选用建议

  1. 优先使用:实现 Runnable 接口 绝大多数业务场景(多线程售票、异步任务、线程池任务)首选 Runnable,扩展性、资源共享、工程规范更优,也是阿里、美团等大厂编码规范推荐写法。
  2. 极少场景使用:继承 Thread 仅当线程类确定不需要继承其他类、任务无需多线程共享数据、逻辑极简时临时使用,项目开发中不推荐作为主流方案。

六、拓展:补充 Callable

JDK1.5 新增Callable接口,同样基于任务分离思想,支持线程返回值、抛出异常,本质是 Runnable 的升级,延续了任务与线程分离的设计思想,进一步印证 Runnable 架构的优越性。

结语

两种线程创建本质没有性能优劣,选型核心在于面向对象设计与业务场景。Runnable 依托接口多实现、解耦、易共享的特性,成为工业级多线程开发的标准范式;Thread 继承仅作为基础入门理解使用。

更多推荐