Java面试必问!run() 和 start() 方法到底有啥区别?

简介: 在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。



大家好!我是小米,今天我们来聊一个经常出现在 Java 面试中的经典面试题:线程的 run() 和 start() 有什么区别?为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这个问题看似简单,其实背后涉及到多线程的运行机制、线程生命周期的管理,甚至是 Java 内部的底层实现。现在,就让我们通过一个故事来一起揭秘这个问题,帮助你在面试中更自信地应对这一难题!

一场关于线程的小故事

假设你正在准备一个大型的在线直播系统,在高并发场景下,你需要确保每个用户的直播流都能够并行处理。为了解决这个问题,你决定在程序中使用 Java 的多线程机制来实现。

你写了一个简单的线程类,代码如下:

这段代码看起来很简单,它实现了 Thread 类并重写了 run() 方法。你可能会想到,调用 run() 方法就可以启动线程并执行直播流处理了。

但是,当你运行下面的代码时,事情变得有点不对劲:

问题来了:你发现程序不会启动新线程,而只是同步执行了 run() 方法里的内容!这究竟是为什么呢?难道 run() 方法有问题吗?我们明明是继承了 Thread 类,重写了 run() 方法,难道这就是启动线程的方式吗?

run() 方法和 start() 方法的区别

在 Java 中,线程的启动不是直接调用 run() 方法就能完成的。实际上,run() 方法是线程的入口点,它包含了线程执行的代码,但它并不会直接启动新的线程。

1、run() 方法

run() 方法只是一个普通的方法,它的作用是描述线程应该执行的任务。如果你直接调用 run() 方法,那么它就像是一个普通的函数调用,在当前线程中执行,而并没有创建新线程。

也就是说,调用 run() 方法时,代码不会在新的线程中执行,而是会在当前线程中顺序执行。所以,在我们上面的代码中,thread.run() 其实就是直接调用了 run() 方法,而没有启动新的线程。

2、start() 方法

而 start() 方法才是启动新线程的正确方式。当我们调用 start() 方法时,它会做两件事:

  • 启动一个新的线程。
  • 在新的线程中调用我们在 run() 方法中写的代码。

总结:start() 方法实际上是一个底层的操作,它会告诉 JVM 创建一个新的线程,并调用 run() 方法。而 run() 方法仅仅是线程执行的入口,它不会自动启动新线程。简单来说,start() 负责启动线程,run() 负责执行线程任务

为什么我们不能直接调用 run() 方法?

那么,问题就来了:既然 run() 方法是线程任务的实现,为什么我们不能直接调用 run() 方法来启动线程呢?这似乎让人摸不着头脑。

原因就在于: 直接调用 run() 方法并不会创建新的线程,它只是将 run() 方法的代码当作普通的方法调用来执行。在这种情况下,线程任务仍然是在当前线程中同步执行的,而不是在新线程中并行执行。

我们回到之前的代码,如果我们像这样调用 run():

这时,run() 方法的代码依然是在主线程中执行的,而不是在新的线程中执行。程序并没有并行地执行线程任务,实际上它只是将线程任务当做普通的任务来执行了。因此,我们看到的输出就是:

它是在主线程中执行的,而不是在新的线程中并行执行的。

start() 方法底层的实现

那我们再来深入探讨一下,start() 方法背后到底做了些什么?如果你深入到 Java 的源代码中,你会发现,start() 方法是调用了 Thread 类中的 start0() 方法,这个方法会在底层启动一个新的操作系统线程,并在新线程中调用 run() 方法。

简单来说:

  • start() 方法:启动一个新的线程,并在该线程中执行 run() 方法。
  • run() 方法:线程的执行内容,是线程开始执行后运行的代码。

线程的生命周期

为了更好地理解 start() 和 run() 的区别,我们还需要了解线程的生命周期。在 Java 中,线程的生命周期一般分为以下几个状态:

  • 新建(New):线程刚被创建,还没有启动。
  • 就绪(Runnable):线程已经启动,但可能还没有开始执行,等待 CPU 分配时间。
  • 运行中(Running):线程获得 CPU 时间片并正在执行任务。
  • 阻塞(Blocked):线程被阻塞,等待某个资源或事件的发生。
  • 终止(Terminated):线程执行完毕,生命周期结束。

start() 方法在底层实际上会让线程进入 就绪 状态,从而使得线程能够进入 运行中 状态并执行 run() 方法。

小结:为什么要使用 start()?

我们回到最初的问题:为什么我们需要使用 start() 方法来启动线程,而不能直接调用 run() 方法?

  • run() 方法是线程的任务代码,它并不会创建新的线程,而是当前线程直接执行。
  • start() 方法负责启动新线程,并且在新线程中调用 run() 方法。

所以,如果我们想要在新的线程中并行执行任务,就必须调用 start() 方法来启动线程,而不能直接调用 run() 方法。直接调用 run() 方法就相当于将线程任务当作普通的函数来执行,根本不会并行运行。

小贴士:多线程编程的常见错误

在实际开发中,我们经常会遇到一些常见的多线程错误,了解并避免这些问题非常重要。

  • 误用 run() 方法:很多初学者误以为调用 run() 方法就能启动线程,其实这只会在当前线程中执行 run() 方法。正确的方式是调用 start()。
  • 线程安全问题:在多线程环境中,多个线程同时操作共享资源时,容易发生线程安全问题。需要使用 synchronized 或者其他线程安全的工具类来避免竞态条件。
  • 忘记线程同步:在一些场景下,多线程共享资源时,如果没有正确同步,可能会导致不可预测的结果。

END

今天我们通过一个简单的故事,深入了解了 Java 中 run() 和 start() 方法的区别,解答了为什么我们调用 start() 方法时会执行 run() 方法,以及为什么不能直接调用 run() 方法来启动线程。掌握这些基本概念,对我们在面试中的表现和日常的多线程开发都大有裨益。

希望这篇文章对你有所帮助,如果你喜欢这样的内容,记得点赞和分享哦!下次我们继续聊更多有趣的 Java 面试题!

公众号对技术型文章的推送机制有所调整,需要大家多多点赞在看转发收藏,才能让更多技术同行们能看到优质的技术分享~

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
3月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
1614 102
|
4月前
|
存储 缓存 人工智能
Java int和Integer的区别
本文介绍了Java中int与Integer的区别及==与equals的比较机制。Integer是int的包装类,支持null值。使用==比较时,int直接比较数值,而Integer比较对象地址;在-128至127范围内的Integer值可缓存,超出该范围或使用new创建时则返回不同对象。equals方法则始终比较实际数值。
150 0
|
2月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
297 1
|
5月前
|
人工智能 前端开发 Java
Java 面试资料中相关代码使用方法与组件封装方法解析
这是一份详尽的Java面试资料代码指南,涵盖使用方法与组件封装技巧。内容包括环境准备(JDK 8+、Maven/Gradle)、核心类示例(问题管理、学习进度跟踪)、Web应用部署(Spring Boot、前端框架)、单元测试及API封装。通过问题库管理、数据访问组件、学习进度服务和REST接口等模块化设计,帮助开发者高效组织与复用功能,同时支持扩展如用户认证、AI推荐等功能。适用于Java核心技术学习与面试备考,提升编程与设计能力。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
131 6
Java 面试资料中相关代码使用方法与组件封装方法解析
|
5月前
|
存储 Java C语言
Java List 复制:浅拷贝与深拷贝方法及区别
我是小假 期待与你的下一次相遇 ~
526 1
|
4月前
|
安全 算法 Java
Java 中 synchronized 与 AtomicInteger 的区别
在Java多线程编程中,`synchronized`和`AtomicInteger`均用于实现线程安全,但原理与适用场景不同。`synchronized`是基于对象锁的同步机制,适用于复杂逻辑和多变量同步,如银行转账;而`AtomicInteger`采用CAS算法,适合单一变量的原子操作,例如计数器更新。二者各有优劣,应根据具体需求选择使用。
123 0
|
5月前
|
算法 Java 数据库连接
Java 与 C++ 区别深入剖析及应用实例详解
本文深入剖析了Java和C++两种编程语言的区别,从编译与执行机制、面向对象特性、数据类型与变量、内存管理、异常处理等方面进行对比,并结合游戏开发、企业级应用开发、操作系统与嵌入式开发等实际场景分析其特点。Java以跨平台性强、自动内存管理著称,适合企业级应用;C++则因高性能和对硬件的直接访问能力,在游戏引擎和嵌入式系统中占据优势。开发者可根据项目需求选择合适语言,提升开发效率与软件质量。附面试资料链接:[点此获取](https://pan.quark.cn/s/4459235fee85)。
456 0
|
7月前
|
存储 算法 架构师
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
|
7月前
|
人工智能 算法 数据库
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
美团面试:LLM大模型存在哪些问题?RAG 优化有哪些方法?_
|
6月前
|
Java
Java 中 Exception 和 Error 的区别
在 Java 中,`Exception` 和 `Error` 都是 `Throwable` 的子类,用于表示程序运行时的异常情况。`Exception` 表示可被捕获和处理的异常,分为受检异常(Checked)和非受检异常(Unchecked),通常用于程序级别的错误处理。而 `Error` 表示严重的系统级问题,如内存不足或 JVM 错误,一般不建议捕获和处理。编写程序时应重点关注 `Exception` 的处理,确保程序稳定性。
163 0