摘要: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方法,结果如下所示。

thread1
thread2
thread3

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

thread1
thread3
thread2

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

thread2
thread3
thread1

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

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

如何确保线程的执行顺序

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类,结果如下所示。

thread1
thread2
thread3

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

thread1
thread2
thread3

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

thread1
thread2
thread3

可以看到,每次运行的结果都是相同的,所以,使用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中线程的执行顺序不仅能够在面试中让你脱颖而出,更能够让你在平时的工作中,迅速定位由于多线程并发问题导致的“诡异”问题,让你从工作中脱颖而出。

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

面试官:Java中线程是按什么顺序执行的?的更多相关文章

  1. java中线程分两种,守护线程和用户线程。

    java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...

  2. java中线程机制

    java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...

  3. Java中线程的使用 (2)-多线程、线程优先级、线程睡眠、让步、阻塞

    Java中线程的使用 (2)-多线程.线程优先级.线程睡眠.让步.阻塞 (一)多线程使用方法 说明:创建每个新的线程,一定要记得启动每个新的线程(调用.start()方法) class Xc3 ext ...

  4. Java中线程的实现:

    Java中线程的实现: 一.线程简介: 实现的两种方式为: 1.Thread类 2.Runnable接口 都在java.lang中 都有共通的方法:public void run() 二.线程常用方法 ...

  5. JAVA中线程同步方法

    JAVA中线程同步方法 1  wait方法:         该方法属于Object的方法,wait方法的作用是使得当前调用wait方法所在部分(代码块)的线程停止执行,并释放当前获得的调用wait所 ...

  6. 多线程(三) java中线程的简单使用

    java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...

  7. Java中线程池的实现原理

    知识点总结 ---------------------------------------------------------------------------------------------- ...

  8. Java中线程池的实现原理-求职必备

    jdk1.5引入Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行.被哪个线程执行,以及什么时候执行. 初始化线程池(4种) ...

  9. Java中线程池,你真的会用吗?

    在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...

随机推荐

  1. vue常用方法封装-一键安装使用(赠送免费工具)

    相信大家在使用vue开发过程中一定遇到了各种方法的整理收集,每次遇到新的问题都需要找到合适的方法 这里我给大家封装了一些vue项目中常用到的方法合集,免费提供费大家 因此,jsoften横空出世,不为 ...

  2. CF102920L Two Buildings【分治】【决策单调性】

    优秀的分治题目.是"2020-2021 ACM-ICPC, Asia Seoul Regional Contest"的一道题. Description There are \(n\ ...

  3. css中的transform,transition,translate的关系

    transform 旋转(transform是没有动画效果,你改变了它的值,元素的样子就唰的改变了.其中的位移的函数名就叫translate,所以说,translate是transform的一部分.) ...

  4. 开源OA办公平台功能介绍:应用市场-固定资产管理(一)功能设计

    概述 应用市场-固定资产管理,是用来维护管理企业固定资产的一个功能.其整个功能包括对固定资产的台账信息.领用.调拨.借用.维修.盘点.报废等一整个生命周期的动态管理过程.力求客户安装就可以使用. 本应 ...

  5. 处理XML数据应用实践

    摘要:GaussDB(DWS)支持XML数据类型及丰富的XML解析函数,可实现关系数据和XML数据的映射管理功能. XML概述 XML是可扩展的标识语言(eXtensible Markup Langu ...

  6. Java 搭建 RabbitMq 消息中间件

    前言 当系统中出现"生产"和"消费"的速度或稳定性等因素不一致的时候,就需要消息队列. 名词 exchange: 交换机 routingkey: 路由key q ...

  7. Oracle RMAN scripts to delete archivelog

    vi del_arch.shexport ORACLE_SID=pdcsdbrman target / cmdfile=/home/oracle/scripts/del_arch.sql log=/h ...

  8. 【图像处理】使用OpenCV进行图像处理教程(一)

    OpenCV是进行图像处理的工具,也是计算机视觉领域近十几年不断发展和完善的产物.面对这个已基本成熟的开源库知识体系,我们新生代有必要不断地总结.回顾,以新的视角快速融入计算机视觉的奥秘世界. 从这篇 ...

  9. 反射的常用API

    反射的常用API 加载程序集 Assembly assembly = Assembly.Load("程序集名称"); // 从前目录加载程序集,提供程序集名称,无后缀 Assemb ...

  10. windows 之间内网开启远程桌面连接

    win7设置远程桌面1.找到我的电脑\计算机图标,右键"属性"如图2.进入系统和安全设置-选项卡中,找到"远程设置"右上角位置点击打开3.在随后的"系 ...