Java 浅析Thread.join()
概要
本文分三个部分对Thread.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()的更多相关文章
- Java 浅析 Thread.join()
概要 本文分为三部分对 Thread.join() 进行分析: 1. join() 的示例和作用 2. join() 源码分析 3. 对网上其他分析 join() 的文章提出疑问 1. join() ...
- java 多线程 Thread.join子线程结束父线程再运行;join(long):等待超时毫秒数
Join的使用 目的:当子线程运行结束后,父线程才能再继续运行 /** * @ClassName ThreadJoinExample * @projectName: object1 * @author ...
- Java java.lang.Thread#join()方法分析
结论:A 线程调用 B 线程对象的 join 方法,则 A 线程会被阻塞,直到 B 线程 挂掉 (Java Doc 原话: Watis for this thread to die). 一.分析 查看 ...
- [译]Java Thread join示例与详解
Java Thread join示例与详解 Java Thread join方法用来暂停当前线程直到join操作上的线程结束.java中有三个重载的join方法: public final void ...
- Java Thread.join的作用和原理
很多人对Thread.join的作用以及实现了解得很少,毕竟这个api我们很少使用.这篇文章仍然会结合使用及原理进行深度分析 内容导航 Thread.join的作用 Thread.join的实现原理 ...
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...
- Java并发编程的艺术笔记(三)——Thread.join()
t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒.并不影响同一时刻处在运行状态的其他线程.它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程.能够使得线程 ...
- 浅析Thread的join() 方法
Thread中的 join() 方法在实际开发过程中可能用的不是很多,但是在面试中作为考察基本功知识的扎实与否,经常会被用到.因此,对于 Thread 的 join() 方法进行了一定的研究. 常见的 ...
- JDK源码那些事儿之浅析Thread上篇
JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JD ...
随机推荐
- Serializable 都这么牛逼了,Parcelable 还要你何用?
一些闲聊 距离上一篇文章似乎又是很久了,看起来也没有很多反馈,催更就更不用说了.哈哈,放弃了. 话说最近公司在招聘一批至少 5 年开发经验的 Android 开发工程师,我也是忙开了花,激动得不行呀. ...
- 简单购物车程序(Python)
#简单购物车程序:money_all=0tag=Trueshop_car=[]shop_info={'apple':10,'tesla':100000,'mac':3000,'lenovo':3000 ...
- 整合SSH时,遇到了org.springframework.beans.factory.BeanCreationException错误
严重: StandardWrapper.Throwableorg.springframework.beans.factory.BeanCreationException: Error creating ...
- Python中的选择排序
选择排序 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理如下.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大 ...
- 网页设计——2. html入门
开始正式的课程讲解了,首先来看看课程体系: Java EE(java 企业应用程序版本) java2 有三个版本:J2 SE(标准版),J2 EE(企业版).J2 ME(微缩版). 我们要掌握J2EE ...
- IIS网站本机可以访问但局域网其他机器无法访问 解决方法
在IIS部署网站的时候,发现只有本机可以访问,通过localhost和IP地址都可以,但是当局域网其他机器访问我的网站的时候,无响应. 我的是WIN10的系统 解决方法: 网站所对应的端口必须对外 ...
- JavaScript学习笔记(一)数组排序
数组(Array)在JavaScript中是非常常用的类型,关于数组的排序,与C#等语法中的排序,乍看相似,其实差别比较大. Array的排序方法有两个,分别是reverse()和sort(). re ...
- google guava cache缓存基本使用讲解
代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...
- 利用vmware搭建分布式集群
背景: 我们需要至少3台服务器来实现分布式,鉴于没那么多钱买真机器,从学习和开发的角度看,只有虚拟机一条路了. 软件选择: 虚拟机使用VMware软件,因为主流而且资料比较多,学习成 ...
- 3.python元组与列表
Python的元组与列表类似,同样可通过索引访问,支持异构,任意嵌套.不同之处在于元组的元素不能修改.元组使用小括号,列表使用方括号. 创建元组 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开 ...