应优先使用runnable接口而非继承thread类,因java单继承限制、线程池兼容性、职责分离及lambda支持等优势;run()是普通方法调用,start()才真正启动新线程。

Thread类直接继承会破坏类的单一继承结构
Java只允许单继承,如果你的业务类已经继承了某个父类(比如 Service 或 AbstractProcessor),再继承 Thread 就会编译报错:java.lang.Error: Unresolved compilation problem: The type X cannot subclass Thread because it already extends Y。
这时候必须换用 Runnable——它是个接口,可以和任意继承关系共存。
- 除非你写的是一个“纯粹的线程封装类”,且确定永远不会被其他类继承,否则别碰
Thread继承 -
Thread子类里重写的run()方法,和Runnable实现类里的run()语义完全一致,但调用路径不同:前者是new MyThread().start(),后者是new Thread(new MyRunnable()).start() - 继承
Thread后,你其实把“线程控制逻辑”和“任务逻辑”耦合在了一起;而Runnable更清晰地分离了“做什么”和“谁来跑”
Runnable接口实现更适合线程池场景
所有标准线程池(ExecutorService)接受的都是 Runnable 或 Callable,不是 Thread 实例。传入 Thread 对象会导致编译失败或运行时异常:java.lang.ClassCastException: java.lang.Thread cannot be cast to java.lang.Runnable。
哪怕你只是临时起个线程,也建议默认用 Runnable,避免后续迁移到线程池时返工。
立即学习“Java免费学习笔记(深入)”;
- 线程池中复用的是
Thread实例,真正执行的是你传进去的Runnable,所以任务逻辑必须可重复执行、无状态或状态可控 - 如果任务需要返回值,优先选
Callable而不是硬改Thread类加字段——后者在线程复用下极易引发数据污染 -
Runnable实现类可以是 lambda 表达式,比如executor.submit(() -> doWork());而Thread子类无法这样简化
run() 和 start() 的区别不是“要不要多线程”,而是“是否新建线程”
这是最常踩的坑:thread.run() 不会启动新线程,它只是普通方法调用,代码仍在当前线程同步执行;只有 thread.start() 才触发 JVM 创建 OS 级线程并回调 run()。
错误写法如 new Thread(() -> System.out.println("hi")).run(); —— 输出会立刻出现,但全程单线程,毫无并发意义。
- 一旦调用过
start(),该Thread实例就进入“已启动”状态,再次调用start()会抛出IllegalThreadStateException -
Runnable实例没有start()方法,必须包装进Thread或交给Executor才能异步执行 - 不要在
run()方法里手动捕获并吞掉所有异常——未捕获的Exception会让线程静默终止,很难排查
ThreadLocal 在两种方式下的行为完全一致,但初始化时机容易错
ThreadLocal 的值绑定到当前线程,跟你是怎么创建线程无关。但新手常误以为“在 Thread 子类构造器里 set 值,就能让 run() 里拿到”,结果发现是 null。
因为 ThreadLocal.set() 必须在目标线程内执行才有效——子类构造器运行在创建线程里(比如 main),不是 run() 所在的线程。
- 正确做法是在
run()开头或Runnable.run()里做tl.set(...) - 如果用线程池,
ThreadLocal值可能被复用线程残留,务必在使用后tl.remove(),尤其在 Web 容器或长生命周期服务中 - 不要把大对象塞进
ThreadLocal,容易引发内存泄漏,特别是配合线程池时










