大家好!我是小米,今天我们来聊一个经常出现在 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岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!