概要

本文分三个部分对Thread.join()进行分析:

1. join() 的示例和作用

2. join() 源码分析

3. 对网上其他分析 join() 的文章提出疑问

1. join() 的示例和作用

1.1 示例

 // 父线程
public class Parent {
public static void main(String[] args) {
// 创建child对象,此时child表示的线程处于NEW状态
Child child = new Child();
// child表示的线程转换为RUNNABLE状态
child.start();
// 等待child线程运行完再继续运行
child.join();
}
}
 // 子线程
public class Child extends Thread {
public void run() {
// ...
}
}

上面代码展示了两个类:Parent(父线程类),Child(子线程类)。

Parent.main()方法是程序的入口,通过 Child child = new Child(); 新建child子线程(此时 child子线程处于NEW状态);

然后调用child.start()(child子线程状态转换为RUNNABLE);

再调用child.join(),此时,Parent父线程会等待child子线程运行完再继续运行。

下图是我总结的 Java 线程状态转换图:

1.2 join() 的作用

让父线程等待子线程结束之后才能继续运行

我们来看看在 Java 7 Concurrency Cookbook 中相关的描述(很清楚地说明了 join() 的作用):

Waiting for the finalization of a thread

In 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.

当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

2. join() 源码分析

以下是 JDK 8 中 join() 的源码:

 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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
} public final synchronized void join(long millis, int nanos)
throws InterruptedException { if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
} if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
} join(millis);
}

join() 一共有三个重载版本,分别是无参、一个参数、两个参数:

 public final void join() throws InterruptedException;

 public final synchronized void join(long millis) throws InterruptedException;

 public final synchronized void join(long millis, int nanos) throws InterruptedException;

其中

(1) 三个方法都被final修饰,无法被子类重写。

(2) join(long), join(long, long) 是synchronized method,同步的对象是当前线程实例。

(2) 无参版本和两个参数版本最终都调用了一个参数的版本。

(3) join() 和 join(0) 是等价的,表示一直等下去;join(非0)表示等待一段时间。

从源码可以看到 join(0) 调用了Object.wait(0),其中Object.wait(0) 会一直等待,直到被notify/中断才返回。

while(isAlive())是为了防止子线程伪唤醒(spurious wakeup),只要子线程没有TERMINATED的,父线程就需要继续等下去。

(4) join() 和 sleep() 一样,可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了 wait(),会出让锁,而 sleep() 会一直保持锁。

以本文开头的代码为例,我们分析一下代码逻辑:

调用链:Parent.main() -> child.join() -> child.join(0) -> child.wait(0)(此时 Parent线程会获得 child 实例作为锁,其他线程可以进入 child.join() ,但不可以进入 child.join(0), 因为child.join(0)是同步方法)。

如果 child 线程是 Active,则调用 child.wait(0)(为了防止子线程 spurious wakeup, 需要将 wait(0) 放入 while(isAlive()) 循环中。

一旦 child 线程不为 Active (状态为 TERMINATED), child.notifyAll() 会被调用-> child.wait(0)返回 -> child.join(0)返回 -> child.join()返回 -> Parent.main()继续执行, 子线程会调用this.notify(),child.wait(0)会返回到child.join(0) ,child.join(0)会返回到 child.join(), child.join() 会返回到 Parent 父线程,Parent 父线程就可以继续运行下去了。

3. 对网上其他分析 join() 的文章提出疑问

我觉得网上很多文章的描述有歧义,下面挑选一些描述进行分析,也欢迎大家留言一起讨论。

a. 子线程结束之后,"会唤醒主线程",父线程重新获取cpu执行权,继续运行。

这里感谢kerwinX的留言,子线程结束后,子线程的this.notifyAll()会被调用,join()返回,父线程只要获取到锁和CPU,就可以继续运行下去了。

b. join() 将几个并行的线程"合并为一个单线程"执行。

我理解这个说法的意思,但是这样描述只会让读者更难理解。

在调用 join() 方法的程序中,原来的多个线程仍然多个线程,并没有发生“合并为一个单线程”。真正发生的是调用 join() 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。

一点感想:技术人员写作技术文章时,最好尽量避免使用过于口语化的词汇。

因为这种词汇歧义比较大,会让读者感到更加困惑或形成错误的理解。

Java 浅析 Thread.join()的更多相关文章

  1. Java 浅析Thread.join()

    概要 本文分为三部分对 Thread.join() 进行分析: 1. join() 的示例和作用 2. join() 源码分析 3. 对网上其他分析 join() 的文章提出疑问 1. join() ...

  2. java 多线程 Thread.join子线程结束父线程再运行;join(long):等待超时毫秒数

    Join的使用 目的:当子线程运行结束后,父线程才能再继续运行 /** * @ClassName ThreadJoinExample * @projectName: object1 * @author ...

  3. Java java.lang.Thread#join()方法分析

    结论:A 线程调用 B 线程对象的 join 方法,则 A 线程会被阻塞,直到 B 线程 挂掉 (Java Doc 原话: Watis for this thread to die). 一.分析 查看 ...

  4. [译]Java Thread join示例与详解

    Java Thread join示例与详解 Java Thread join方法用来暂停当前线程直到join操作上的线程结束.java中有三个重载的join方法: public final void ...

  5. Java Thread.join的作用和原理

    很多人对Thread.join的作用以及实现了解得很少,毕竟这个api我们很少使用.这篇文章仍然会结合使用及原理进行深度分析 内容导航 Thread.join的作用 Thread.join的实现原理 ...

  6. java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

  7. Java并发编程的艺术笔记(三)——Thread.join()

    t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒.并不影响同一时刻处在运行状态的其他线程.它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程.能够使得线程 ...

  8. 浅析Thread的join() 方法

    Thread中的 join() 方法在实际开发过程中可能用的不是很多,但是在面试中作为考察基本功知识的扎实与否,经常会被用到.因此,对于 Thread 的 join() 方法进行了一定的研究. 常见的 ...

  9. JDK源码那些事儿之浅析Thread上篇

    JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JD ...

随机推荐

  1. MySQL 中Index Condition Pushdown (ICP 索引条件下推)和Multi-Range Read(MRR 索引多范围查找)查询优化

    一.ICP优化原理 Index Condition Pushdown (ICP),也称为索引条件下推,体现在执行计划的上是会出现Using index condition(Extra列,当然Extra ...

  2. 《Python》内置方法进阶和常用模块

    一:__new__ 在__init__之前,实例化对象的第一步是__new__创建了一个空间 class Foo: def __init__(self): # 初始化方法 print('执行了init ...

  3. (C/C++学习笔记) 十六. 预处理

    十六. 预处理 ● 关键字typeof 作用: 为一个已有的数据类型起一个或多个别名(alias), 从而增加了代码的可读性. typedef known_type_name new_type_nam ...

  4. 再谈Contacts中姓氏多音字排序错误问题

    说到中国人的名字,那就不得不考虑多音字的问题,比如'单',在作为姓氏时应该读作'shan'而不是'dan'.但是在Contacts程序中却使用的是'D'来作为bucket label!这是为什么?如何 ...

  5. 关于junk jack

    旨在利用一切物品打造你自己的世界.你需要砍树获得木头,挖各种矿石用火炉炼成不同东西,顺便得到不同颜色材质的石头建房子,打各种家禽获得吃的甚至烘焙.养起来繁殖,天上掉下的星星.捡到圣诞礼包中的袜子都可以 ...

  6. jquery中使用jsonp

    1 .关于jsonp的原理等,请看下面的文章,说的很明白. http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery. ...

  7. 序列化 NSKeyedArchiver,NSPropertyListSerialization

    到目前为止,看到oc实现的序列化方式有两种:NSKeyedArchiver,NSPropertyListSerialization. 在这两种序列化方式中,NSData都是序列化的目标.两种方式的不同 ...

  8. word2vec 小测试

    Bag-of-words Model Previous state-of-the-art document representations were based on the bag-of-words ...

  9. TypeScript 编译器源码研究(一)

    TypeScript (以下简称 TS)是一个非常强大的语言,其编译器源码超过 10000 行. 源码在 Github 可以找到:https://github.com/Microsoft/TypeSc ...

  10. setsebool命令详解与SELinux管理

    setsebool命令是用来修改SElinux策略内各项规则的布尔值.setsebool命令和getsebool命令是SELinux修改和查询布尔值的一套工具组.SELinux的策略与规则管理相关命令 ...