作为一名Java开发者,在多线程编程的入门阶段,很多人都会遇到一个经典问题:Thread.start()
和 Thread.run()
看起来都能执行我们定义的任务,但它们到底有什么区别?为什么官方文档强调要用 start()
方法来启动线程?
本文将用一个极其生动的比喻,带你彻底弄懂这两者的本质区别,让你从此不再混淆。
一、一个经典的"做饭与玩手机"比喻
想象一个场景:你想吃饭 ,同时还想玩手机。你有两种选择:
-
选择一(错误示范) :自己亲自去厨房,洗菜、切菜、炒菜。在整个做饭的20分钟里,你的双手被占用,完全没办法碰手机。直到你把饭端到桌上,才能开始玩手机。
- 这,就是直接调用
run()
方法。
- 这,就是直接调用
-
选择二(正确做法) :打开手机APP叫一份外卖。你只花了1分钟点餐 ,然后就可以立刻往沙发上一躺,开心地玩19分钟手机。外卖员会在后台独立完成取餐、送餐的过程,饭好了自然会送到你手上。
- 这,就是调用
start()
方法。
- 这,就是调用
看到这里,你已经直观地感受到了两者的巨大差异。下面我们从技术层面深入剖析。
二、技术本质:什么是start()和run()?
run()
方法: 它只是一个普通的方法 。它定义了线程要执行的任务内容 ,就像一份"菜谱"。当你直接调用thread.run()
时,就相当于你(当前线程)亲自按照这份菜谱去做菜。这不会产生任何新的线程。start()
方法: 它是一个神奇的启动命令 。它的作用是为run()
方法里的"菜谱"聘请一位专门的厨师(新线程) 。JVM在收到start()
指令后,会创建一个新的执行线程,并由这个新线程 在后台 自动去调用和执行run()
方法。
三、核心区别对比表
特性 | start() 方法 (叫外卖) |
run() 方法 (自己做饭) |
---|---|---|
本质 | 线程启动器,JVM级别操作 | 普通方法,对象级别操作 |
行为 | 异步执行 (Asynchronous) | 同步执行 (Synchronous) |
线程管理 | 创建新线程,由JVM线程调度器管理 | 不创建新线程,使用当前线程 |
执行结果 | 真正实现多线程并发 | 仅是单线程顺序执行 |
调用限制 | 一个线程对象只能调用一次,否则抛异常 | 可以像普通方法一样多次调用 |
四、让代码说话:看输出结果如何印证一切
Talk is cheap, show me the code. 让我们用一段代码来亲眼验证这个区别。
java
csharp
public class StartVsRunDemo {
public static void main(String[] args) {
// 创建一个线程,任务就是"做饭"
Thread chefThread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": 开始切菜...");
try { Thread.sleep(2000); } catch (Exception e) {} // 模拟耗时
System.out.println(Thread.currentThread().getName() + ": 开始炒菜...");
try { Thread.sleep(2000); } catch (Exception e) {} // 模拟耗时
System.out.println(Thread.currentThread().getName() + ": 菜做好了!");
});
System.out.println("【直接调用run()的后果】");
chefThread.run(); // 错误做法:直接调用run
System.out.println("main: 我终于可以玩手机了!\n"); // 必须等run执行完
// 重新初始化线程,因为一个对象只能start一次
chefThread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": 开始切菜...");
try { Thread.sleep(2000); } catch (Exception e) {}
System.out.println(Thread.currentThread().getName() + ": 开始炒菜...");
try { Thread.sleep(2000); } catch (Exception e) {}
System.out.println(Thread.currentThread().getName() + ": 菜做好了!");
});
System.out.println("【正确调用start()的效果】");
chefThread.start(); // 正确做法:调用start
System.out.println("main: 点完餐了,我玩手机去了!"); // 立即执行,无需等待
}
}
运行结果分析:
text
less
【直接调用run()的后果】
main: 开始切菜... // 线程名是main!主线程自己在干活
main: 开始炒菜... // 主线程被阻塞了,什么都干不了
main: 菜做好了!
main: 我终于可以玩手机了! // 所有活干完才能执行这里
【正确调用start()的效果】
main: 点完餐了,我玩手机去了! // 主线程立即输出, liberated!
Thread-0: 开始切菜... // 新线程Thread-0在后台启动并工作
Thread-0: 开始炒菜... // 新线程在后台继续工作
Thread-0: 菜做好了! // 新线程完成任务
关键发现:
- 调用
run()
时,所有输出的线程名都是main
,证明是主线程一个人在同步劳动。 - 调用
start()
后,主线程的打印语句立刻输出 ,而做饭的任务是由新线程Thread-0
在后台异步完成的,完美诠释了并发。
五、总结与最佳实践
start()
和 run()
的区别,是Java多线程编程的基石概念。
run()
:是任务本身。它定义了"做什么"。start()
:是执行任务的机制 。它定义了"如何做"、"由谁做"------即在一个新的、独立的线程中做。
最佳实践:
永远不要直接调用 run()
方法来试图启动线程。 这是一个常见的初学者错误,它完全违背了多线程的初衷。如果你想实现并发,让任务在后台执行,从而不阻塞主线程,那么唯一正确的选择就是调用 start()
方法。
理解了这个区别,你就拿到了正确进入Java并发世界大门的钥匙。