写点什么

面试官:Java 中线程是按什么顺序执行的?

发布于: 2021 年 04 月 15 日

​​摘要:Java 中多线程并发的执行顺序历来是面试中的重点,掌握 Java 中线程的执行顺序不仅能够在面试中让你脱颖而出,更能够让你在平时的工作中,迅速定位由于多线程并发问题导致的“诡异”问题,让你从工作中脱颖而出。


本文分享自华为云社区《【高并发系列】Java中线程到底是按什么顺序执行的?你了解的可能是错误的!》,原文作者:冰 河 。


Java 中线程的执行顺序可能与你想的不一样!一文解密多线程的执行顺序,调用 Thread 的 start()方法启动线程时,线程的执行顺序是不确定的。也就是说,在同一个方法中,连续创建多个线程后,调用线程的 start()方法的顺序并不能决定线程的执行顺序。

线程的执行顺序是不确定的


调用 Thread 的 start()方法启动线程时,线程的执行顺序是不确定的。也就是说,在同一个方法中,连续创建多个线程后,调用线程的 start()方法的顺序并不能决定线程的执行顺序。


例如,这里,看一个简单的示例程序,如下所示。


package io.binghe.concurrent.lab03;
/** * @author binghe * @version 1.0.0 * @description 线程的顺序,直接调用Thread.start()方法执行不能确保线程的执行顺序 */public class ThreadSort01 { public static void main(String[] args){ Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); });
thread1.start(); thread2.start(); thread3.start(); }}
复制代码


​在 ThreadSort01 类中分别创建了三个不同的线程,thread1、thread2 和 thread3,接下来,在程序中按照顺序分别调用 thread1.start()、thread2.start()和 thread3.start()方法来分别启动三个不同的线程。


那么,问题来了,线程的执行顺序是否按照 thread1、thread2 和 thread3 的顺序执行呢?运行 ThreadSort01 的 main 方法,结果如下所示。


thread1thread2thread3
复制代码


​再次运行时,结果如下所示。


thread1thread3thread2
复制代码


​第三次运行时,结果如下所示。


thread2thread3thread1
复制代码


注意:每个人运行的情况可能都不一样。


可以看到,每次运行程序时,线程的执行顺序可能不同。线程的启动顺序并不能决定线程的执行顺序。

如何确保线程的执行顺序

1.确保线程执行顺序的简单示例


在实际业务场景中,有时,后启动的线程可能需要依赖先启动的线程执行完成才能正确的执行线程中的业务逻辑。此时,就需要确保线程的执行顺序。那么如何确保线程的执行顺序呢?


可以使用 Thread 类中的 join()方法来确保线程的执行顺序。例如,下面的测试代码。


package io.binghe.concurrent.lab03;/** * @author binghe * @version 1.0.0 * @description 线程的顺序,Thread.join()方法能够确保线程的执行顺序 */public class ThreadSort02 {    public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); });
thread1.start();
//实际上让主线程等待子线程执行完成 thread1.join();
thread2.start(); thread2.join();
thread3.start(); thread3.join(); }}
复制代码


​可以看到,ThreadSort02 类比 ThreadSort01 类,在每个线程的启动方法下面添加了调用线程的 join()方法。此时,运行 ThreadSort02 类,结果如下所示。


thread1thread2thread3
复制代码


再次运行时,结果如下所示。


thread1thread2thread3
复制代码


​第三次运行时,结果如下所示。


thread1thread2thread3
复制代码


可以看到,每次运行的结果都是相同的,所以,使用 Thread 的 join()方法能够保证线程的先后执行顺序。

2.join 方法如何确保线程的执行顺序


既然 Thread 类的 join()方法能够确保线程的执行顺序,我们就一起来看看 Thread 类的 join()方法到底是个什么鬼。


进入 Thread 的 join()方法,如下所示。


public final void join() throws InterruptedException {	join(0);}
复制代码


​可以看到 join()方法调用同类中的一个有参 join()方法,并传递参数 0。继续跟进代码,如下所示。


public final synchronized void join(long millis)throws InterruptedException {	long base = System.currentTimeMillis();	long now = 0;
if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); }
if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } }}
复制代码


可以看到,有一个 long 类型参数的 join()方法使用了 synchroinzed 修饰,说明这个方法同一时刻只能被一个实例或者方法调用。由于,传递的参数为 0,所以,程序会进入如下代码逻辑。


if (millis == 0) {	while (isAlive()) {		wait(0);	}}
复制代码


​首先,在代码中以 while 循环的方式来判断当前线程是否已经启动处于活跃状态,如果已经启动处于活跃状态,则调用同类中的 wait()方法,并传递参数 0。继续跟进 wait()方法,如下所示。


public final native void wait(long timeout) throws InterruptedException;
复制代码


可以看到,wait()方法是一个本地方法,通过 JNI 的方式调用 JDK 底层的方法来使线程等待执行完成。


需要注意的是,调用线程的 wait()方法时,会使主线程处于等待状态,等待子线程执行完成后再次向下执行。也就是说,在 ThreadSort02 类的 main()方法中,调用子线程的 join()方法,会阻塞 main()方法的执行,当子线程执行完成后,main()方法会继续向下执行,启动第二个子线程,并执行子线程的业务逻辑,以此类推。


总结:Java 中多线程并发的执行顺序历来是面试中的重点,掌握 Java 中线程的执行顺序不仅能够在面试中让你脱颖而出,更能够让你在平时的工作中,迅速定位由于多线程并发问题导致的“诡异”问题,让你从工作中脱颖而出。


点击关注,第一时间了解华为云新鲜技术~

发布于: 2021 年 04 月 15 日阅读数: 15
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
面试官:Java中线程是按什么顺序执行的?