文章目录
-
- [一、先说结论:run() 和 start() 的核心区别](#一、先说结论:run() 和 start() 的核心区别)
- [二、直接调用 run():根本没有新线程](#二、直接调用 run():根本没有新线程)
-
- [start() 源码做了什么?](#start() 源码做了什么?)
- [三、调两次 start():直接报错](#三、调两次 start():直接报错)
- 四、正确姿势:需要新线程就创建新对象
- [五、Thread 的状态机:为什么只能 start 一次](#五、Thread 的状态机:为什么只能 start 一次)
- [run() vs start() 全景](#run() vs start() 全景)
- 回答技巧与点评
新手写多线程,最容易犯两个错:一是直接调 run() 而不是 start(),二是同一个线程对象调两次 start()。前者"线程"根本没启动,后者直接抛异常。但你有没有想过,为什么 Java 要这么设计?
搞懂了这两个问题,你对线程生命周期的理解就跨过了"会用"的门槛。
一、先说结论:run() 和 start() 的核心区别
| 维度 | start() | run() |
|---|---|---|
| 作用 | 启动新线程,由 JVM 调用 run() | 在当前线程中执行 run() 方法体 |
| 是否创建新线程 | ✅ 是 | ❌ 否 |
| 调用次数 | 只能调一次 | 可以反复调 |
| 重复调用 | 抛 IllegalThreadStateException | 正常执行 |
| 底层机制 | native start0() 创建 OS 线程 | 普通方法调用 |
一句话记住:start() 是"开工仪式",run() 是"工作内容"------仪式只能搞一次,但活可以反复干。
二、直接调用 run():根本没有新线程
java
Thread t = new Thread(() -> {
System.out.println("当前线程: " + Thread.currentThread().getName());
});
t.run(); // 输出:当前线程: main 👈 在主线程执行的!
t.start(); // 输出:当前线程: Thread-0 👈 这才是新线程
调 run() 就像: 你叫了一个外卖,但没下单(没 start),自己跑去店里吃了------外卖员(新线程)根本没出动。
调 start() 就像: 你正式下了单,外卖员出发送货------新线程启动了,run() 会在新线程中被 JVM 自动调用。
start() 源码做了什么?
java
public synchronized void start() {
if (threadStatus != 0) // 状态检查 👈
throw new IllegalThreadStateException();
group.add(this);
start0(); // native 方法,创建 OS 线程 👈
}
private native void start0(); // JVM 实现,真正创建线程
关键流程: start() 调用 native start0() → JVM 创建操作系统线程 → 新线程执行 run()。直接调 run() 绕过了这整条链路,只是普通方法调用。
三、调两次 start():直接报错
java
Thread t = new Thread(task);
t.start(); // 正常启动
t.start(); // IllegalThreadStateException! 👈
看看源码为什么报错:
java
public synchronized void start() {
if (threadStatus != 0) // 已经启动过,threadStatus != 0 👈
throw new IllegalThreadStateException();
...
}
为什么不让调两次? 因为线程的生命周期是单向的:
NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED
线程一旦启动,就回不到 NEW 状态。你没法让一个已经跑完的线程"复活",就像没法让烧完的蜡烛再点一次------你得拿一根新的。
生活类比: 火箭发射。倒计时结束、火箭升空,你不能对着同一个火箭再按一次发射按钮------要么它还在飞(已经启动了),要么已经烧完了(TERMINATED)。
四、正确姿势:需要新线程就创建新对象
java
// ❌ 复用同一个 Thread 对象
Thread t = new Thread(task);
t.start();
t.start(); // 报错!
// ✅ 创建新的 Thread 对象
new Thread(task).start();
new Thread(task).start(); // 两个新线程,没问题 👈
// ✅ 更好的方式:用线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(task);
pool.submit(task); // 线程池自动复用线程 👈
五、Thread 的状态机:为什么只能 start 一次
start()
NEW ──────────────────→ RUNNABLE
│
┌──────────┼──────────┐
↓ ↓ ↓
BLOCKED WAITING TIMED_WAITING
│ │ │
└──────────┴──────────┘
│
↓
TERMINATED
threadStatus 值对应:
- 0 = NEW(还没 start)
- 其他值 = 已启动(RUNNABLE/BLOCKED/WAITING/TERMINATED 等)
start() 检查的就是这个值,非 0 直接拒绝。
run() vs start() 全景
run() vs start() 全景
start()
├── 作用:启动新线程
├── 底层:native start0() → 创建 OS 线程 → JVM 回调 run()
├── 只能调用一次,重复调用抛 IllegalThreadStateException
└── 线程状态:NEW → RUNNABLE
run()
├── 作用:在当前线程执行任务
├── 底层:普通方法调用,无新线程
├── 可以反复调用(就是普通方法)
└── 线程状态:无变化
线程生命周期(单向不可逆)
NEW → RUNNABLE → TERMINATED
↕
BLOCKED / WAITING / TIMED_WAITING
口诀:start 建线程调一次,run 是内容可反复,
线程生命周期单行道,start 过了不回头。
回答技巧与点评
标准回答
直接调用 run() 不会创建新线程,只是在当前线程中执行 run() 方法体,等同于普通方法调用。start() 才会调用 native start0() 创建操作系统线程,由 JVM 在新线程中回调 run()。调用两次 start() 会抛 IllegalThreadStateException,因为线程生命周期是单向的,start() 内部会检查 threadStatus,非 0(即已启动过)就抛异常。需要再次执行任务应该创建新的 Thread 对象或使用线程池。
加分回答
- 设计哲学:线程状态的单向流转是操作系统的底层设计------OS 线程一旦终止,其资源(栈、寄存器等)已被回收,Java 不可能在 JVM 层面"复活"一个 OS 线程。IllegalThreadStateException 是 Java 对底层不可逆性的上层表达
- Runnable 的优势:正是因为 Thread 不能重复 start,才更应该用 Runnable------任务是可复用的,Thread 对象是一次性的。把"任务"和"执行载体"分离,既省资源又灵活
- 线程池的设计动机:线程池的核心价值就是"线程复用"------避免了反复创建销毁 OS 线程的开销。Worker 线程跑完一个任务不退出,而是循环取下一个任务,本质上是绕过了"线程只能 start 一次"的限制
面试官点评
这道题考的是你对线程生命周期和底层机制的理解。能说出"run() 不创建新线程"只是入门,能讲清 start0() 的 native 调用、threadStatus 的状态检查、以及线程生命周期单向不可逆的设计原因,才说明你真正理解了线程的本质。如果你还能引出线程池的复用设计,面试官会认为你有全局视野。
内容有帮助?点赞、收藏、关注三连!评论区等你 💪