start()是JVM层面的线程启动指令,创建新线程并触发run()执行;run()只是普通实例方法,直接调用仍在当前线程同步执行,不产生并发效果。

混淆 start() 与 run(),本质是把“并发启动”错当成“方法调用”,结果所有本该并行的任务被强行压进单一线程串行执行——这不是并发失效,而是并发假象。线上系统一旦出现响应延迟飙升、吞吐量骤降、CPU 使用率却偏低的组合症状,八成源于此。
直接调用 run() = 主线程自我封印
run() 就是一个普通实例方法,没有线程创建能力,也没有调度逻辑。你写 thread.run(),等同于写 doSomething():JVM 不会分配新栈、不切换上下文、不触发调度器。所有耗时操作(比如数据库查询、HTTP 调用、循环计算)都在当前线程里堵着执行,后续代码必须等它彻底结束才能继续。
- 主线程卡住 → 接口超时、定时任务堆积、消息积压
- 看似“起了多个 Thread 对象”,实则全是纸老虎,线程数监控永远显示 1
- 日志里看不到线程名切换(如
T-1、T-2),全都是main或同一个工作线程名
start() 才是真正打开并发开关的动作
start() 是 JVM 层面的线程生命周期指令。它内部调用 native 方法 start0(),由操作系统完成线程资源分配(内核栈、PCB、TID),并将线程状态从 NEW 置为 RUNNABLE。此时 JVM 调度器才开始介入,让新线程和调用方线程各自独立运行。
- 调用
thread.start()后,主线程立刻返回,不等待子线程完成 - 子线程在自己的栈空间执行
run(),互不抢占 CPU 和栈帧 - 线程 dump 中能清晰看到多个处于
RUNNABLE或WAITING状态的独立线程
为什么错误总在测试阶段逃逸?
小流量、低并发、纯内存计算的测试环境,run() 和 start() 表现几乎一致:功能都跑通、结果都正确、耗时差异不明显。但一到生产环境,I/O 等待放大、请求并发上升、线程竞争加剧,run() 的同步阻塞缺陷就集中爆发。
- 本地调试用
run()没问题 → 上线后接口平均响应从 50ms 涨到 2s+ - 单元测试只校验输出 → 忽略了线程数、上下文切换、锁持有时间等并发指标
- 日志打点全在
run()方法体里 → 看似“执行了”,实则没并发
如何一眼识别代码里的隐患?
重点扫描三类高危模式:
- Thread 实例被 new 出来后,调用的是 .run() 而非 .start()
- 使用 Lambda 创建 Thread 时,误写成
new Thread(() -> {...}).run() - 自定义线程类中,构造完对象后手动调用
myThread.run(),而非myThread.start()
只要发现任何对 run() 的显式调用,且上下文不是用于调试或模拟,就应立即重构。

















