摘要:基于如何让多线程按照自己指定的顺序执行这个场景,浅谈Thread中join()函数的作用和原理。

join的作用

   之前有人问过我一个这样的面试题:如何让多线程按照自己指定的顺序执行?这个问题最简单的回答是通过Thread.join来实现。

   让父线程等待子线程结束之后才能继续运行。我们来看看在 Java 7 Concurrency Cookbook 中相关的描述(很清楚地说明了 join() 的作用):

Waiting for the finalization of a threadIn some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

   黑体部分英文的大意是当我们用某个线程调用这个方法时,join方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。下面用一个示例验证一下:

public class JoinDemo extends Thread {
int i;
Thread previousThread; //上一个线程 public JoinDemo(Thread previousThread, int i) {
this.previousThread = previousThread;
this.i = i;
} @Override
public void run() {
try {
//调用上一个线程的join方法,自己演示的时候可以把这行代码注释掉
previousThread.join();
Object aa = new Object();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(previousThread.getName() + ", num:" + i);
} public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
previousThread.setName("parent thread");
for (int i = 0; i < 10; i++) {
JoinDemo joinDemo = new JoinDemo(previousThread, i);
joinDemo.start();
previousThread = joinDemo;
previousThread.setName("child thread " + i);
}
}
}

   上面的代码,注意 previousThread.join()部分,大家可以把这行代码注释以后看看运行效果,在没有加join的时候运行的结果是不确定的,加了join以后运行结果按照递增的顺序输出。

源码分析

public class Thread implements Runnable {
...
public final void join() throws InterruptedException {
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) { //判断是否携带阻塞的超时时间,等于0表示没有设置超时时间
while (isAlive()) {//isAlive获取线程状态,无线等待直到previousThread线程结束
wait(0); //调用Object中的wait方法实现线程的阻塞
}
} else { //阻塞直到超时
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
...

   由此可见,join()方法是通过调用Object中的wait()函数实现线程的阻塞。main线程在调用previousThread.join()时,会持有线程对象previousThread的锁(wait 意味着拿到该对象的锁),然后调用previousThread的wait方法造成主线程阻塞,直到该对象唤醒main线程(即子线程previousThread执行完毕退出的时候)。synchronized修饰在方法层面相当于synchronized(this),this就是previousThread本身的实例。

   子线程结束后,子线程previousThread的this.notifyAll()会被调用,join()返回,主线程只要获取到锁和CPU执行权,就可以继续执行了。

小结

   首先join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了previousThread.join()方法,相当于在previousThread.join()代码这块写了一个同步代码块,谁去执行了这段代码呢?是主线程,所以主线程被wait()了。然后在子线程previousThread执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有previousThread这个对象锁的线程,也就是主线程,使主线程继续执行。

Thread.join使用场景

   在实际使用过程中,我们可以通过join方法来等待线程执行的结果,其实有点类似future/callable的功能。通过以下伪代码来说明join的使用场景:

public void joinDemo(){
//....
Thread t=new Thread(payService);
t.start();
//....
//其它业务逻辑处理,不需要确定t线程是否执行完
insertData();
//后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程的执行结束
t.join();
}

Reference

Java中如何让多线程按照自己指定的顺序执行的更多相关文章

  1. 在java中怎样实现多线程?线程的4种状态

    一.在java中怎样实现多线程? extends Thread implement Runnable 方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 ...

  2. Java 中 try、catch、finally 语句块的执行顺序

    假设代码顺序书写如下:try → catch → finally → 其他代码 则: 1.正常执行顺序:try → catch → finally → 其他代码 2.try,catch和finally ...

  3. Java中的线程--多线程面试题

    到这里,基本上线程的并发中的知识点都是学到了,到了最后,还有三道面试题,从面试题中学习更加的加深一下,多线程中的知识点,如何在实际的问题中来解决多线程的问题,可以更好的从实际出发 一.面试题1 面试题 ...

  4. Java中,对多线程访问同一变量(并发访问)的认识

    在Java中,如果启动多个线程对同一个对象或者变量时候,在没有安全保护前提下有可能会抛出并异常 java.util.ConcurrentModificationException 当方法检测到对象的并 ...

  5. Java中如何判断当前环境是大端字节顺序还是小端字节顺序

    Java非字节类型的基本类型,除了布尔型都是由组合在一起的几个字节组成的.这些数据类 型及其大小总结在表 2-1 中. 表:基本数据类型及其大小 数据类型 大小(以字节表示) Byte 1 Char ...

  6. Java中try、finally语句中有return时的执行情况 [转]

    原文:http://kingj.iteye.com/blog/1436761 在Java中当try.finally语句中包含return语句时,执行情况到底是怎样的,finally中的代码是否执行,大 ...

  7. JAVA中GC时finalize()方法是不是一定会被执行?

    在回答上面问题之前,我们一定要了解JVM在进行垃圾回收时的机制,首先: 一.可达性算法  要知道对象什么时候死亡,我们需要先知道JVM的GC是如何判断对象是可以回收的.JAVA是通过可达性算法来来判断 ...

  8. JAVA中try、catch、finally带return的执行顺序总结

    异常处理中,try.catch.finally的执行顺序,大家都知道是按顺序执行的.即,如果try中没有异常,则顺序为try→finally,如果try中有异常,则顺序为try→catch→final ...

  9. java中HashMap在多线程环境下引起CPU100%的问题解决(转)

    最近项目中出现了Tomcat占用CPU100%的情况,原以为是代码中出现死循环,后台使用jstack做了dump,发现是系统中不合理使用HashMap导致出现了死循环(注意不是死锁). 产生这个死循环 ...

  10. Java面试题:Java中怎么样实现多线程

    方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可. 下面是一个例子: public class MyThrea ...

随机推荐

  1. jmespath 使用及案例

    什么是jmespath jmespath 是python里面的一个库 主要在httprunner框架里使用 2.使用语法 列表: with_jmespath(jmes_path,var_name) m ...

  2. 三分钟掌握音视频处理 | 在 Rust 中优雅地使用 FFmpeg

    前言 音视频处理看似高深莫测,但在开发中,我们或多或少都会遇到相关需求,比如视频格式转换.剪辑.添加水印.音频提取等. FFmpeg 作为行业标准,几乎无所不能,很多流行的软件(如 VLC.YouTu ...

  3. 推荐2款docker可视化管理面板

    1.portainer portainer算是比较知名的开源docker管理工具,功能上比较齐全,不过只有英文版本,没有中文,不过这并不影响他的使用. 官方地址:https://www.portain ...

  4. 【Web】Servlet三大作用域、JSP四大作用域

    request 生命周期: 创建:客户端向服务器发送一次请求,服务器就会创建request对象. 销毁:服务器对这次请求作出响应后就会销毁request对象. 有效:仅在当前请求中有效. 作用:常用于 ...

  5. android点滴-1

    一.关于TSpeedButtons 1.对于TspeedButtons,需要选择适当的StyleLookUp值后,才能在ObjectInspector中出现TintColor属性,根据自己需要进行修改 ...

  6. Python读取CSV文件并存储到MySQL

    在项目中对后台进行测试时,经常会遇到要在数据库新增数据,那么如何快速新增数据来提高工作效率呢? 现整理如下: 代码内容(csv_to_mysql.py): # coding=utf-8import p ...

  7. idea的配置优化

    一.显示工具条 二.设置鼠标悬浮提示 三.显示方法分隔符 四.忽略大小写提示 五.主题设置 如果需要很好看的编码风格,这里有很多主题 http://color-themes.com/?view=ind ...

  8. Python3_数据类型和变量

    Python3_数据类型和变量 一.数据类型 Python有五个标准的数据类型: Numbers(数字) String(字符串) List(列表) Tuple(元组) Dictionary(字典) 在 ...

  9. 服务器时间漂移,如何开启Linux NTP自动同步

    前言 在日常服务器运维中,我们往往默认服务器的时间是精准的.但最近一次偶然的 date 查询,让我发现--服务器时间竟然悄悄地漂移了-- 本文记录了整个排查与解决的过程,希望能帮到遇到类似问题的朋友, ...

  10. NOIP集训 P4137 Rmq Problem / mex 题解

    前置指使:可持久化线段树 题解:P4137 Rmq Problem / mex 有一个长度为 \(n\) 的数组 \(\{ a_1,a_2,...,a_n \}\) . \(m\) 次询问,每次询问一 ...