调用 run() 不会启动新线程,仅是普通方法调用;start() 才是真正启动线程的唯一合法入口,触发 jvm 创建 os 线程并调度执行 run()。

直接调用 run() 不会启动新线程,只是普通方法调用
这是最常踩的坑:看到 Thread 对象就下意识写 t.run(),结果代码还是单线程顺序执行。因为 run() 在 Thread 类里就是一个被重写的普通实例方法,它的源码本质是:
@Override<br>public void run() {<br> if (target != null) {<br> target.run();<br> }<br>}也就是说,它只是转发给你传进来的 Runnable 实例的 run() 方法——完全不涉及线程创建、调度或状态变更。
- 主线程调用
t.run()→ 所有逻辑仍在主线程中同步阻塞执行 - 哪怕你在
run()里写了Thread.sleep(5000),主线程也会卡住 5 秒才继续 - 多个
t1.run()、t2.run()是严格串行的,毫无并发性
start() 才是真正启动线程的唯一合法入口
start() 是 JVM 级别的“开关”:它触发线程生命周期的正式开始。调用后,JVM 会为该线程分配栈空间、设置状态为 RUNNABLE(注意不是“正在运行”,而是“可被调度”),并最终在某个 CPU 时间片内自动回调该线程的 run() 方法——这个回调由 JVM 底层完成,你无法手动控制时机。
- 必须且只能调用一次
start();第二次调用会抛出IllegalThreadStateException - 调用
start()后,主线程立即继续往下走,不等子线程执行完 - 子线程中的
System.out.println(Thread.currentThread().getName())会输出类似t1,而非main
常见错误现象:输出顺序暴露了线程真相
用一个极简对比就能看清区别:
Thread t = new Thread(() -> {<br> System.out.println("in run: " + Thread.currentThread().getName());<br>});<br>System.out.println("before");<br>t.run();<br>System.out.println("after");
输出一定是:before → in run: main → after
立即学习“Java免费学习笔记(深入)”;
换成 t.start() 后,输出可能是:before → after → in run: Thread-0(也可能穿插,取决于调度)
- 关键信号:如果
run()里打印的线程名是main,说明根本没开新线程 - 如果两个线程交替打印(如
t1-0,t2-0,t1-1…),那一定是用了start() - IDE 调试时看线程堆栈:只有
start()后才能在 Debug 视图里看到独立的线程条目
为什么不能绕过 start() 自己“模拟”线程启动?
有人会想:“既然 run() 就是干活的,我用 ExecutorService 提交 Runnable 不也一样?”——逻辑对,但机制不同。重点在于:start() 是 JVM 线程模型的契约入口,它触发的是 OS 级线程创建(通过 native start0()),而任何 Java 层的“调用 run()”都只是方法栈帧切换,不产生新 OS 线程、不新增调度单元、不改变线程安全语义。
- 自定义线程池提交任务,本质仍是让池中已有线程去调用你的
run(),不是让你绕过start() - 反射调用
start0()?非法,且不可移植,JVM 不保证其行为 - 继承
Thread却重写start()?危险,破坏状态机,大概率触发异常
真正需要记住的只有一句:start() 是线程诞生的出生证明,run() 只是它长大后干的活——出生证不能手写,活可以重写,但不能把出生证撕了自己去干活。








