Java 线程创建:继承Thread子类 vs 直接new Thread,到底有什么区别?

在 Java 多线程基础学习和面试中,最经典的问题之一:

继承 Thread 类创建线程 和 直接 new Thread() 传入 Runnable 创建线程,有什么本质区别?

很多同学只会写代码,但说不清底层差异、设计缺陷、适用场景,面试极易扣分。

今天我用一篇博客,彻底讲透两种写法的语法、原理、优缺点、资源共享、设计思想、企业最佳实践


一、两种线程创建方式代码还原

1. 方式一:自定义类继承 Thread 子类

核心逻辑:继承 Thread 类,重写 run() 方法,线程对象本身就是任务。

// 自定义线程类,继承Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的任务逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println("线程执行:" + Thread.currentThread().getName() + "  i=" + i);
        }
    }
}

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

特点:线程对象和任务耦合在一起

2. 方式二:直接 new Thread + Runnable

核心逻辑:Thread 只负责线程调度,Runnable 专门负责任务逻辑,线程和任务解耦

public class RunnableTest {
    public static void main(String[] args) {
        // 定义独立任务
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程执行:" + Thread.currentThread().getName() + "  i=" + i);
            }
        };

        // 多个线程共用同一个任务
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
    }
}


二、核心区别对照表(面试必背)

对比维度

继承 Thread 子类

new Thread + Runnable

设计思想

继承(is-a)

组合(has-a)

耦合度

高(线程=任务)

低(线程、任务分离)

Java单继承限制

有,无法再继承其他类

无,可正常继承、多实现接口

任务共享

困难,每个线程独立任务

轻松,多线程共享同一个Runnable

代码复用性

高,任务可复用、可交给线程池

扩展性

强,适配线程池、Callable

开发推荐

不推荐

✅ 官方推荐


三、深度原理讲解

1. 继承 Thread 的最大缺陷:单继承限制

Java 是单继承机制

如果你的类 extends Thread,就不能再继承其他业务父类

在业务开发中,实体类、工具类、业务父类非常普遍,一旦继承 Thread,直接废掉类的扩展性。

Runnable 是接口,一个类可以:

  • 继承一个父类

  • 同时实现多个接口

完全不影响业务结构。

2. 任务与线程是否解耦,是核心差距

继承 Thread 方式:

任务逻辑写在 Thread 子类内部,任务属于线程,任务不能单独剥离复用。

Runnable 方式:

任务是独立的 Runnable 对象,线程只是一个“执行者”。

同一个任务可以:

  • 被多个线程并行执行

  • 交给线程池执行

  • 随时替换、修改

3. 资源共享场景(面试高频)

需求:三个线程同时卖 100 张票(共享资源)。

Runnable 轻松实现共享


class TicketTask implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + " 卖票:" + ticket--);
        }
    }
}

// 三个线程共享同一个任务对象
public class Test {
    public static void main(String[] args) {
        TicketTask task = new TicketTask();
        new Thread(task,"窗口1").start();
        new Thread(task,"窗口2").start();
        new Thread(task,"窗口3").start();
    }
}

继承 Thread 无法直接共享

每 new 一次子类,就是一个独立线程对象,变量不共享,必须加 static 静态变量强行共享,代码丑陋且线程不安全。


四、常见误区:run() 和 start() 千万别混用

两种创建方式通用规则:

  • start():调用 native 方法,向操作系统申请开启新线程,最终执行 run(),真正多线程

  • run():只是普通成员方法调用,不开启新线程,串行执行

面试高频坑:直接调用 run() 不会创建新线程!


五、企业开发最终结论

✅ 永远推荐:Thread + Runnable(Lambda)

理由:

  1. 符合 组合优于继承 的设计思想

  2. 规避单继承限制,类结构更优雅

  3. 任务与线程解耦,复用性极强

  4. 天然支持多线程资源共享

  5. 无缝对接线程池、Future、异步任务

❌ 禁止使用:继承 Thread 子类

除了教学演示,实际项目中几乎没有使用场景


六、全文总结

1. 继承 Thread 是继承耦合,受单继承限制,无法共享任务,复用性差,不推荐;

2. 直接 new Thread + Runnable 是组合解耦,线程和任务分离,支持资源共享、高复用、易扩展;

3. 开发统一标准:一律使用 Runnable / 线程池,禁止继承 Thread

4. 开启线程必须调用 start(),不能直接调用 run()

看似简单的线程创建方式,背后是 Java 优秀的设计思想:组合优于继承、职责单一、解耦复用。掌握这两种方式的区别,是学好多线程、搞定面试、写出高质量并发代码的基础。

更多推荐