【面试专栏|Java并发编程】Thread类核心方法全解:start和run的区别
引言
Thread类是Java多线程编程的核心,其内部的start()/run()、wait()/notify()、sleep()/join()等方法,是实现线程控制、协作的关键,也是面试高频考点。很多开发者只会用,却不懂原理,容易混淆用法、踩坑出错。本文从原理、代码示例、区别对比、面试官追问四个维度,全解Thread类核心方法,帮你吃透用法、应对面试,建议点赞收藏备用~
文章目录
一、THREAD类核心方法全解(原理+示例)
Thread类的核心方法围绕线程的启动、暂停、等待、唤醒、协作展开,我们按“功能分组”拆解,结合代码示例,让每个方法的用法和原理一目了然。
1. 线程启动核心:start() vs run()(最易混淆)
这是Thread类最基础、最易混淆的两个方法,核心作用是“启动线程”,但本质差异极大,面试必问!
1.1 start() 方法(真正启动线程)
核心原理
start()方法是Thread类的native方法(底层由C/C++实现),调用后会向JVM申请启动一个新线程,JVM会分配线程资源、调度线程,最终调用run()方法执行线程逻辑。注意:start()方法只能调用一次,多次调用会抛出IllegalThreadStateException异常。
代码示例
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":线程执行中");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":线程执行完成");
}
}
public class StartVsRunTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("测试线程");
// 调用start()方法,真正启动新线程
thread.start();
// 错误示范:多次调用start(),会抛出异常
// thread.start();
}
}
关键注意点
- 调用start()后,线程进入“就绪状态”,等待CPU调度,并非立即执行。
- 每个线程对象的start()方法只能调用一次,重复调用会报错。
1.2 run() 方法(线程执行逻辑入口)
核心原理
run()方法是Thread类的普通方法,仅用于定义线程的执行逻辑,直接调用run()不会启动新线程,只是普通的方法调用,会在当前线程(主线程)中执行,无法实现多线程效果。
代码示例(对比start())
public class StartVsRunTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("测试线程");
// 直接调用run(),不会启动新线程,在主线程中执行
thread.run();
System.out.println("当前线程:" + Thread.currentThread().getName()); // 输出:当前线程:main
}
}
核心对比(一句话记住)
- start():启动新线程,JVM调用run(),多线程执行。
- run():普通方法调用,无新线程启动,单线程执行。
2. 线程暂停核心:sleep() 方法
sleep()方法用于让当前线程“暂停执行指定时间”,释放CPU资源,但不会释放锁资源,是最常用的线程暂停方法。
核心原理
sleep(long millis) 是Thread类的静态方法,调用后当前线程会进入“阻塞状态”,持续指定的毫秒数,时间到后自动进入“就绪状态”,等待CPU调度。期间,线程持有的锁(synchronized锁)不会释放,其他线程无法获取该锁。
代码示例
public class SleepTest {
public static void main(String[] args) {
// 线程1:执行sleep(),暂停1秒
new Thread(() -> {
System.out.println("线程1:开始执行,准备暂停1秒");
try {
Thread.sleep(1000); // 暂停1000毫秒(1秒)
} catch (InterruptedException e) {
// 睡眠被中断时抛出异常,需处理
e.printStackTrace();
}
System.out.println("线程1:暂停结束,继续执行");
}, "线程1").start();
// 线程2:不暂停,直接执行
new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("线程2:执行第" + (i+1) + "次");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程2").start();
}
}
关键注意点
- sleep()是静态方法,作用于“当前线程”,而非调用该方法的线程对象(比如Thread t = new Thread(); Thread.sleep(1000); 是主线程睡眠,不是t线程)。
- 调用sleep()时,必须捕获InterruptedException异常(受检异常)。
- 睡眠期间不释放锁,若持有synchronized锁,其他线程会一直等待。
3. 线程等待核心:join() 方法
join()方法用于让“当前线程”等待“调用join()的线程”执行完成后,再继续执行,本质是实现线程间的顺序执行。
核心原理
join()方法是Thread类的实例方法,调用thread.join()后,当前线程会进入“阻塞状态”,直到thread线程执行完成(或等待超时),才会被唤醒,继续执行。常用于需要“先执行完某个线程,再执行当前线程”的场景(比如主线程等待子线程执行完,再处理结果)。
代码示例(主线程等待子线程)
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread subThread = new Thread(() -> {
System.out.println("子线程:开始执行");
try {
Thread.sleep(2000); // 子线程执行耗时2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程:执行完成");
}, "子线程");
subThread.start();
// 主线程调用join(),等待子线程执行完成后再继续
subThread.join();
System.out.println("主线程:子线程执行完成,我开始执行");
}
}
重载方法说明
- join():无参,当前线程无限等待,直到目标线程执行完成。
- join(long millis):有参,当前线程等待指定毫秒数,若目标线程仍未执行完成,当前线程会自动唤醒,继续执行。
4. 线程协作核心:wait() vs notify()/notifyAll()
wait()、notify()、notifyAll() 是Object类的方法(非Thread类),用于实现线程间的“协作通信”,比如生产者-消费者模式,核心是释放锁资源,实现线程间的唤醒与等待。
核心前提(必记)
这三个方法必须在synchronized同步代码块(或同步方法)中使用,否则会抛出IllegalMonitorStateException异常。因为它们需要依赖“对象锁”来实现线程的唤醒与等待。
4.1 wait() 方法(线程等待,释放锁)
核心原理
调用object.wait()后,当前线程会释放持有的object对象锁,进入“等待队列”,直到被其他线程调用object.notify()或object.notifyAll()唤醒,才会进入“就绪状态”,重新竞争锁。
4.2 notify() 方法(唤醒单个等待线程)
核心原理
调用object.notify()后,会从object对象的“等待队列”中,随机唤醒一个等待的线程,被唤醒的线程会进入“就绪状态”,等待竞争锁,继续执行。
4.3 notifyAll() 方法(唤醒所有等待线程)
核心原理
调用object.notifyAll()后,会唤醒object对象“等待队列”中所有等待的线程,所有被唤醒的线程进入“就绪状态”,竞争锁,谁先抢到锁,谁先执行。
代码示例(生产者-消费者模式,简单版)
// 共享资源:商品池
class GoodsPool {
private int count = 0; // 商品数量
private final int MAX_COUNT = 5; // 商品池最大容量
// 生产商品(同步方法,持有GoodsPool对象锁)
public synchronized void produce() throws InterruptedException {
// 商品池满,生产者等待
while (count >= MAX_COUNT) {
System.out.println("商品池已满,生产者等待...");
this.wait(); // 释放锁,进入等待队列
}
// 生产商品
count++;
System.out.println("生产者生产1件商品,当前商品数:" + count);
this.notifyAll(); // 唤醒所有等待的消费者
}
// 消费商品(同步方法,持有GoodsPool对象锁)
public synchronized void consume() throws InterruptedException {
// 商品池空,消费者等待
while (count <= 0) {
System.out.println("商品池为空,消费者等待...");
this.wait(); // 释放锁,进入等待队列
}
// 消费商品
count--;
System.out.println("消费者消费1件商品,当前商品数:" + count);
this.notifyAll(); // 唤醒所有等待的生产者
}
}
// 测试类
public class WaitNotifyTest {
public static void main(String[] args) {
GoodsPool goodsPool = new GoodsPool();
// 生产者线程(3个)
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
while (true) {
goodsPool.produce();
Thread.sleep(500); // 模拟生产耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者" + (i+1)).start();
}
// 消费者线程(2个)
for (int i = 0; i < 2; i++) {
new Thread(() -> {
try {
while (true) {
goodsPool.consume();
Thread.sleep(1000); // 模拟消费耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者" + (i+1)).start();
}
}
}

关键注意点
- wait()释放锁,sleep()不释放锁(核心区别)。
- notify()随机唤醒一个线程,notifyAll()唤醒所有线程,根据场景选择(避免死等)。
- 等待条件必须用while循环判断(而非if),防止“虚假唤醒”(线程被唤醒后,条件可能已不满足)。
5. Thread类核心方法整体流程(mermaid图)
二、核心方法对比与避坑指南(高频考点)

为了方便大家快速区分、避免踩坑,整理了核心方法的关键对比,重点记忆差异点:
| 方法 | 所属类 | 核心作用 | 是否释放锁 | 调用场景 | 关键注意点 |
|---|---|---|---|---|---|
| start() | Thread | 启动新线程 | 无(未持有锁) | 启动线程时 | 只能调用一次,否则报错 |
| run() | Thread | 定义线程执行逻辑 | 无(未持有锁) | 无需启动新线程时 | 直接调用是普通方法,单线程执行 |
| sleep(long) | Thread | 暂停当前线程指定时间 | 不释放 | 线程暂停、延时执行 | 静态方法,需捕获InterruptedException |
| join() | Thread | 让当前线程等待目标线程完成 | 不释放 | 线程间顺序执行 | 可设置超时时间,避免无限等待 |
| wait() | Object | 让当前线程等待,唤醒后继续 | 释放 | 线程间协作(如生产者-消费者) | 必须在同步代码块中,需被notify唤醒 |
| notify() | Object | 唤醒单个等待线程 | 不释放(唤醒后竞争锁) | 线程间协作 | 必须在同步代码块中,随机唤醒 |
| notifyAll() | Object | 唤醒所有等待线程 | 不释放(唤醒后竞争锁) | 线程间协作 | 必须在同步代码块中,避免死等 |
避坑总结(必记)
- 别用run()启动线程,否则无法实现多线程。
- sleep()不释放锁,wait()释放锁,混淆会导致死锁。
- wait()、notify()、notifyAll()必须在同步代码块中使用,否则报错。
- join()是当前线程等待目标线程,而非目标线程等待当前线程。
- 避免多次调用start(),会抛出非法线程状态异常。
三、面试官追问环节(实战必备)
这部分是面试高频追问,比纯八股文更实用,帮你提前准备面试官的连环提问,建议重点记忆!
追问1:start()和run()的本质区别是什么?(必问)
核心3点,精准回答:
- 线程启动:start()会启动新线程,JVM分配线程资源、调度线程;run()只是普通方法调用,无新线程启动。
- 执行主体:start()的执行主体是JVM,由JVM调用run();run()的执行主体是当前线程(调用者线程)。
- 调用限制:start()只能调用一次,多次调用报错;run()可多次调用,无限制。
追问2:wait()和sleep()的核心差异有哪些?(高频)
最核心的4点差异,避开踩坑:
- 所属类不同:wait()是Object类的方法;sleep()是Thread类的静态方法。
- 锁资源释放:wait()会释放持有的对象锁;sleep()不会释放任何锁。
- 唤醒方式:wait()需要被notify()/notifyAll()唤醒,或等待超时;sleep()时间到后自动唤醒。
- 调用场景:wait()用于线程间协作;sleep()用于线程暂停、延时执行。
追问3:notify()和notifyAll()该如何选择?什么时候用notifyAll()?
分场景选择,避免死等:
- 用notify():当等待队列中只有一个线程需要被唤醒,且唤醒任意一个线程都能满足需求(比如只有一个消费者,唤醒它即可)。
- 用notifyAll():当等待队列中有多个线程,且需要唤醒所有线程竞争锁(比如生产者生产商品后,需要唤醒所有等待的消费者;或避免部分线程被“遗忘”,导致死等)。
补充:实际开发中,notifyAll()使用更广泛,可避免虚假唤醒导致的死锁问题。
追问4:为什么wait()、notify()、notifyAll()要定义在Object类中,而不是Thread类中?
核心原因2点:
- 锁与对象绑定:Java中的锁是“对象锁”(每个对象都有一个锁监视器),wait()、notify()需要操作对象的锁监视器,因此定义在Object类中,让每个对象都能调用。
- 线程与锁分离:一个线程可以持有多个对象的锁,若定义在Thread类中,无法区分操作的是哪个对象的锁,会导致锁管理混乱。
追问5:join()方法的底层实现原理是什么?
底层依赖wait()方法实现,简化原理:
- 调用thread.join()时,当前线程会进入thread对象的等待队列,调用thread.wait()释放锁。
- 当thread线程执行完成(终止)时,JVM会自动调用thread.notifyAll(),唤醒所有等待在thread对象上的线程(即当前线程),当前线程继续执行。
四、总结
本文全面解析了Thread类核心方法(start()/run()、sleep()/join()、wait()/notify()/notifyAll()),从原理、代码示例、对比避坑到面试官追问,覆盖了开发和面试的核心需求,帮你彻底吃透这些高频知识点。
核心要点:
- 启动线程用start(),别用run();
- 暂停用sleep()(不释放锁),协作用wait()(释放锁);
- 顺序执行用join(),线程唤醒用notify()/notifyAll()(需在同步代码块中);
- 面试重点掌握方法间的区别,以及底层原理(如wait()和sleep()的差异、join()的实现)。
📌 本文已收录至我的Java多线程系列专栏,关注我,后续持续更新多线程进阶知识点(线程同步、死锁、线程池实战等)。
💡 如果你有其他Thread类方法相关的疑问,或者想了解某部分的细节,欢迎在评论区留言讨论~
👍 觉得有用的话,点赞+收藏,避免下次找不到!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)