Java中Thread.join()的使用方法

概要

本文分三个部分对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 mayhave a program that will begin initializing the resources it needs before proceeding with therest of the execution. We can run the initialization tasks as threads and wait for its finalizationbefore continuing with the rest of the program.For this purpose, we can use the join() method of the Thread class. When we call thismethod using a thread object, it suspends the execution of the calling thread until the objectcalled 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()内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JAVA CountDownLatch与thread-join()的区别解析

    今天学习CountDownLatch这个类,作用感觉和join很像,然后就百度了一下,看了他们之间的区别.所以在此记录一下. 首先来看一下join,在当前线程中,如果调用某个thread的join方法,那么当前线程就会被阻塞,直到thread线程执行完毕,当前线程才能继续执行.join的原理是,不断的检查thread是否存活,如果存活,那么让当前线程一直wait,直到thread线程终止,线程的this.notifyAll 就会被调用. 我们来看一下这个应用场景:假设现在公司有三个员工A,B,C

  • java 中Thread.join()的使用方法

    java 中Thread.join()的使用方法 如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回. import java.util.concurrent.TimeUnit; /** * 6-13 */ public class Join { public static void main(String[] args) throws Exception { Thread previous = Thread.c

  • Java线程编程中isAlive()和join()的使用详解

    一个线程如何知道另一线程已经结束?Thread类提供了回答此问题的方法. 有两种方法可以判定一个线程是否结束.第一,可以在线程中调用isAlive().这种方法由Thread定义,它的通常形式如下: final boolean isAlive( ) 如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false.但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下: final void join( ) throws InterruptedE

  • Java中Thread.join()的使用方法

    概要 本文分三个部分对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

  • Java中Thread类详解及常用的方法

    目录 一.Thread 的常见构造方法 二.Thread 的常见属性 三.创建线程 四.中断线程 五.线程等待 六.获取线程引用 七.线程休眠 八.线程状态 总结 一.Thread 的常见构造方法 方法 说明 Thread() 创建线程对象 Thread(Runnable target) 使用 Runnable 对象创建线程对象 Thread(String name) 创建线程对象并命名 Thread(Runnable target,String name) 使用 Runnable 对象创建线程

  • Java中Thread类的使用和它的属性

    目录 创建线程 方法一:继承Thread类 方法二:实现Runnable接口中的run()方法 方法三:利用内部类 方法四:使用lambmd表达式 使用线程的好处 Thread类的其他属性和方法 给一个线程起名字 判断一个线程是否存活 Thread的其他常见属性 创建线程 线程的中断 线程的等待 获取线程的引用 线程的休眠 在java中可以进行多线程编程,在java标准库中提供了一个Thread类,来表示线程操作.Thread类可以视为java标准库提供的一组解决多线程编程的一组API. 创建好

  • 深入了解Java中Synchronized的各种使用方法

    目录 Synchronized关键字 Synchronized修饰实例方法 Synchronized修饰静态方法 Sychronized修饰多个方法 Synchronized修饰实例方法代码块 Synchronized修饰静态代码块 应该用什么对象作为锁对象 Synchronized与可见性和重排序 可见性 重排序 总结 在Java当中synchronized通常是用来标记一个方法或者代码块.在Java当中被synchronized标记的代码或者方法在同一个时刻只能够有一个线程执行被synchr

  • C#多线程之Thread中Thread.Join()函数用法分析

    本文实例讲述了C#多线程之Thread中Thread.Join()函数用法.分享给大家供大家参考.具体分析如下: Thread.Join()在MSDN中的解释:Blocks the calling thread until a thread terminates 当NewThread调用Join方法的时候,MainThread就被停止执行, 直到NewThread线程执行完毕. Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));

  • 详解Java中Thread 和Runnable区别

    Thread 和Runnable 关系 Thread类是接口Runnable的一个实现类. public class Thread implements Runnable 源码分析 Thread Threa类运行的时候调用start()方法,源代码如下: 调用start()方法,实际运行的是start0方法,方法声明如下: private native void start0() native表明这个方法是个原生函数,即这个函数是用C/C++实现的,被编译成DLL,由Java调用. native

  • java中sleep方法和wait方法的五个区别

    目录 区别一:语法使用不同 区别二:所属类不同 区别三:唤醒方式不同 区别四:释放锁资源不同 sleep 不释放锁 wait 释放锁 区别五:线程进入状态不同 总结 前言: sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中断,也就是线程在休眠的过程中,如果收到中断信号,都可以进行响应并中断,且都可以抛出 InterruptedException 异常,那 sleep 和 wait 有什么区别呢?接下来,我们一起

  • Java中Thread和Runnable创建线程的方式对比

    目录 一.通过继承Thread创建线程 二.实现Runnable创建检查 三.比较两种创建方式 3.1.多继承 3.2. 数据共享 3.3.线程池 四.源码分析 一.通过继承Thread创建线程 通过继承Thread类,创建一个线程,在主线程中,调用start,让线程处于runnable状态,让系统去运行线程的方法. public class MyThread extends Thread { @Override public void run() { System.out.println("执

  • java中的可变参数使用方法

    java中的可变参数使用方法 可变参数时Java 1.5新增的方法,可变参数方法接收0个或者多个指定类型的参数,可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法.如: public class Test{ public int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; } } 可变参数提供了方便,但是使用可变参

随机推荐