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岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
29天前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
123 1
|
2月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
1112 102
|
3月前
|
存储 缓存 人工智能
Java int和Integer的区别
本文介绍了Java中int与Integer的区别及==与equals的比较机制。Integer是int的包装类,支持null值。使用==比较时,int直接比较数值,而Integer比较对象地址;在-128至127范围内的Integer值可缓存,超出该范围或使用new创建时则返回不同对象。equals方法则始终比较实际数值。
127 0
|
3月前
|
Java Shell Maven
【Azure Container App】构建Java应用镜像时候遇无法编译错误:ERROR [build 10/10] RUN ./mvnw.cmd dependency:go-offline -B -Dproduction package
在部署Java应用到Azure Container App时,构建镜像过程中出现错误:“./mvnw.cmd: No such file or directory”。尽管项目根目录包含mvnw和mvnw.cmd文件,但依然报错。问题出现在Dockerfile构建阶段执行`./mvnw dependency:go-offline`命令时,系统提示找不到可执行文件。经过排查,确认是mvnw文件内容异常所致。最终通过重新生成mvnw文件解决该问题,镜像成功构建。
101 0
|
3月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
345 0
|
3月前
|
安全 算法 Java
Java 中 synchronized 与 AtomicInteger 的区别
在Java多线程编程中,`synchronized`和`AtomicInteger`均用于实现线程安全,但原理与适用场景不同。`synchronized`是基于对象锁的同步机制,适用于复杂逻辑和多变量同步,如银行转账;而`AtomicInteger`采用CAS算法,适合单一变量的原子操作,例如计数器更新。二者各有优劣,应根据具体需求选择使用。
102 0
|
4月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
227 0
|
4月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
126 5
|
4月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
214 1
|
7天前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
51 1