Runnable是函数式接口,Thread是实现Runnable的类;继承Thread受限于单继承,实现Runnable更灵活;必须调用start()而非run()才能真正并发;推荐Runnable配合ExecutorService使用。

Runnable 是接口,Thread 是类
Java 中 Runnable 是一个函数式接口,只定义了 run() 方法;而 Thread 是一个具体类,它实现了 Runnable 接口,并封装了线程的生命周期管理逻辑。这意味着你不能直接“继承多个类”,但可以实现多个接口——所以当你的类 already extends 某个父类时,只能选择实现 Runnable,无法再 extends Thread。
常见错误现象:class MyTask extends Thread implements Runnable —— 多余且误导,Thread 本身已实现 Runnable,无需重复声明。
- 使用场景:需要复用任务逻辑(比如同一
Runnable实例被多个Thread执行)时,必须用Runnable - 参数差异:传给
Thread构造器的Runnable对象是任务体;而继承Thread后重写的run()就是任务体本身 - 性能影响:无本质差异,但
Runnable更轻量——它不携带线程资源,只描述行为
start() 和 run() 的调用效果完全不同
无论你用哪种方式创建线程,都必须调用 start() 才会真正启动新线程并执行 run() 方法;如果直接调用 run(),只是在当前线程同步执行,不会开启新线程。
典型错误:new Thread(new MyTask()).run(); —— 看似像多线程,实则仍是单线程串行执行,毫无并发意义。
立即学习“Java免费学习笔记(深入)”;
- 正确写法:
new Thread(new MyTask()).start();或new MyThreadSubclass().start(); - 继承
Thread时,run()是可重写的方法;实现Runnable时,run()是必须实现的抽象方法 - 一旦
start()被调用,该Thread实例不可再次start(),否则抛出IllegalThreadStateException
线程共享与状态隔离的关键区别
实现 Runnable 后,多个 Thread 可以共用同一个任务实例,因此其成员变量天然共享;而继承 Thread 时,每个子类实例本身就是独立线程,变量默认不共享(除非显式用 static 或外部对象)。
容易踩的坑:用 Runnable 实现计数器任务时,若未加锁或使用原子类,多个线程并发修改同一变量会导致结果错乱;而误以为继承 Thread 就“自动线程安全”,其实只是碰巧没共享数据而已。
- 示例:
Runnable counter = () -> { sharedCount++; };——sharedCount必须是AtomicInteger或加synchronized - 继承
Thread的子类中定义private int count,每个线程有自己的副本,不共享 - 兼容性上无差异,但设计意图更清晰:任务(
Runnable)和执行者(Thread)职责分离
现代 Java 更推荐 Runnable + ExecutorService 组合
直接 new Thread 已不被推荐,尤其在高并发、需复用或管控线程生命周期的场景。JDK 5 引入的 ExecutorService 要求提交的是 Runnable(或 Callable),而非 Thread 实例。
为什么?因为 Thread 创建开销大,且缺乏统一调度、拒绝策略、优雅关闭等能力;而 ExecutorService 管理的是任务队列和工作线程池,天然适配 Runnable。
- 典型写法:
executor.submit(new MyTask());,其中MyTask实现Runnable - 无法把继承
Thread的类直接丢进submit(),必须额外包装成Runnable(比如new Thread(task).start()这种模式在 executor 里无意义) - 如果你看到项目里大量
new Thread(...).start(),基本意味着线程资源未受控,存在 OOM 或上下文切换过载风险
真正复杂的地方不在语法选择,而在于是否理解「任务定义」和「线程调度」的解耦——Runnable 描述“做什么”,Thread 或 ExecutorService 决定“何时、由谁、以何种方式做”。忽略这层分离,哪怕写对了 start(),也容易在扩展性和维护性上栽跟头。










