join方法介绍

2023-11-01

首先给出结论:t.join()方法只会使调用该方法的线程进入t对象的等待池,并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
一、使用方式

join是Thread类的一个方法,启动线程后直接调用,例如:

Thread t = new AThread(); t.start(); t.join();
二、为什么要用join()方法

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

三、join方法的作用

在JDk的API里对于join()方法是:

join

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在主线程调用了join()方法,后面的代码只有等到子线程结束了才能执行。

四、实例

之前对于join()方法只是了解它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行。
public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	t1.start();
	t2.start();
} 

}

class ThreadTest extends Thread {
private String name;
public ThreadTest(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=5;i++){
System.out.println(name+"-"+i);
}
}
}

运行结果:

A-1
B-1
B-2
B-3
A-2
B-4
A-3
B-5
A-4
A-5
可以看出A线程和B线程是交替执行的。
而在其中加入join()方法后(后面的代码都略去了ThreadTest类的定义)

package CSDN;
public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	t1.start();
	t1.join();
	t2.start();
}

}
运行结果:

A-1
A-2
A-3
A-4
A-5
B-1
B-2
B-3
B-4
B-5
显然,使用t1.join()之后,B线程需要等A线程执行完毕之后才能执行。需要注意的是,t1.join()需要等t1.start()执行之后执行才有效果,此外,如果t1.join()放在t2.start()之后的话,仍然会是交替执行,然而并不是没有效果,这点困扰了我很久,也没在别的博客里看到过。

为了深入理解,我们先看一下join()的源码。

/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
    join(0);            //join()等同于join(0)
}
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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);           //join(0)等同于wait(0),即wait无限时间直到被notify
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

可以看出,join()方法的底层是利用wait()方法实现的。可以看出,join方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁,随后进入方法,调用了t1对象的wait()方法,使主线程进入了t1对象的等待池,此时,A线程则还在执行,并且随后的t2.start()还没被执行,因此,B线程也还没开始。等到A线程执行完毕之后,主线程继续执行,走到了t2.start(),B线程才会开始执行。

举个例子,join是在main方法里被调用了。
然后main方法就持有了 join方法 的 这个锁。
然后join 方法里面调用了 wait方法。
这个过程的目的是让持有这个同步锁的线程进入等待。
那么谁持有了这个同步锁呢?
答案就是main方法,因为main方法调用了join方法。
main方法就持有 synchronized 标记的这个锁,谁持有这个锁谁就等待。
wait()方法只会让持有锁的线程进入等待
然后join方法执行完之后,不用想,JVM底层肯定执行了 notify的操作。

网上copy来的jvm代码

//一个c++函数:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;

//这家伙是啥,就是一个线程执行完毕之后,jvm会做的事,做清理啊收尾工作
//里面有一个贼不起眼的一行代码,眼神不好还看不到的呢,就是这个:

ensure_join(this);

//翻译成中文叫 确保_join(这个);代码如下:

static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());

ObjectLocker lock(threadObj, thread);

thread->clear_pending_exception();

java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);

java_lang_Thread::set_thread(threadObj(), NULL);

//thread就是当前线程main线程啊。
lock.notify_all(thread);

thread->clear_pending_exception();
}

notify_all 之后 join方法结束。
调用join()的线程又可以获取到cpu资源了,继续执行join()后面的代码。

此外,对于join()的位置和作用的关系,我们可以用下面的例子来分析

public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	System.out.println(Thread.currentThread().getName()+" start");
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	ThreadTest t3=new ThreadTest("C");
	System.out.println("t1start");
	t1.start();
	System.out.println("t2start");
	t2.start();
	System.out.println("t2end");
	System.out.println("t3start");
	t3.start();
	System.out.println("t3end");
	System.out.println(Thread.currentThread().getName()+" end");
}

}
运行结果为

main start
t1start
t1end
t2start
t2end
t3start
t3end
A-1
A-2
main end
C-1
C-2
C-3
C-4
C-5
A-3
B-1
B-2
B-3
B-4
B-5
A-4
A-5
A、B、C和主线程交替运行。加入join()方法后

public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	System.out.println(Thread.currentThread().getName()+" start");
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	ThreadTest t3=new ThreadTest("C");
	System.out.println("t1start");
	t1.start();
	System.out.println("t1end");
	System.out.println("t2start");
	t2.start();
	System.out.println("t2end");
	t1.join();
	System.out.println("t3start");
	t3.start();
	System.out.println("t3end");
	System.out.println(Thread.currentThread().getName()+" end");
}

}
第1次运行结果

main start
t1start
t1end
t2start
t2end
A-1
A-2
A-3
A-4
A-5
B-1
t3start
B-2
t3end
main end
B-3
B-4
B-5
C-1
C-2
C-3
C-4
C-5
第2次运行结果
main start
t1start
t1end
t2start
t2end
A-1
A-2
A-3
A-4
A-5
t3start
t3end
main end
C-1
C-2
C-3
C-4
C-5
B-1
B-2
B-3
B-4
B-5
第3次运行结果
main start
t1start
t1end
t2start
A-1
A-2
t2end
A-3
B-1
B-2
B-3
A-4
A-5
B-4
B-5
t3start
t3end
main end
C-1
C-2
C-3
C-4
C-5
多次实验可以看出,主线程在t1.join()方法处停止,并需要等待A线程执行完毕后才会执行t3.start(),然而,并不影响B线程的执行。
t.join()方法只会使调用该方法的线程进入t对象的等待池,并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

join方法介绍 的相关文章

  • Qt自绘圆盘图控件

    本人使用QPainter自绘了一个圆盘图 下面这张图片为效果图 图片中的所有 圆 刻度线 字体 均为自绘 没有使用图片 使用方法 在Ui中拖拽一个widget控件 然后右键点击该widget控件 选择提升 话不多说 直接上代码 头文件qdi

随机推荐

  • 数字图像处理-基于Matlab水果识别系统(图片识别)

    文件大小 25M 代码行数 315行 主程序 开发环境 Matlab2016a 下载地址 该源码均通过亲自测试可正常运行 简要概述 图像识别主要是研究用计算机代替人去处理大量的物理信息 从而帮助人们建华劳动 机械分类耗时段的特点很符合水果的
  • 【译】用 Rust 实现 csv 解析-part1

    Rust and CSV parsing 译文 用 Rust 实现 csv 解析 part1 原文链接 https blog burntsushi net csv 原文作者 BurntSushi 译文来自 https github com
  • OSPF实验及配置---超详细

    什么是OSPF 开放式最短路径优先OSPF Open Shortest Path First 是IETF组织开发的一个基于链路状态的内部网关协议 Interior Gateway Protocol 目前针对IPv4协议使用的是OSPF Ve
  • 【改进算法】混合鲸鱼WOA和BAT算法(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文献 1 概述 文献来源 鲸鱼优化算法 whale op
  • 接口测试开发之:Python3,订单并发性能实战

    小屌丝 鱼哥 我想写一个接口订单并发性能 能不能给我讲一下 小鱼 接口订单并发 我前篇文章不是写过常见并发框架 然后你在追加一个创建订单和生成订单不就可以了 小屌丝 鱼哥 你说的可轻松 那你能不能来一个 小鱼 好吧 那我就以我某个项目为例
  • open cvBrisk特征检测与匹配

    什么是BRISK算法 BRISK算法是2011年ICCV上 BRISK Binary Robust Invariant Scalable Keypoints 文章中 提出来的一种特征提取算法 也是一种二进制的特征描述算子 它具有较好的旋转不
  • CentOS 7下安装pptp服务

    一 检查是否支持PPTP 1 在安装之前查看系统是否支持PPTP modprobe ppp compress 18 echo success 应该输出 success 2 是否开启TUN TAP cat dev net tun 应该输出 c
  • Js中读取、移除属性及隐藏组件方法研究

    添加 移除组件属性方法 class名 attr 属性名 属性值 设置指定属性 class名 attr 属性名 读取指定属性值 or document getElementById id值 getAttribute 属性名 class名 re
  • 迪杰斯特拉算法+链式前向星+堆优化

    目录 一 基础 二 使用链式前向星 每次遍历的第一次优化 前向星 链式前向星 1 结构 2 存储边 3 遍历 第一次优化代码 三 堆优化 主要思想 数据类型 四 完整代码 一 基础 直接用邻接矩阵 每次遍历查找来进行操作 void dijk
  • Exchange Online Kiosk产品详细介绍

    目录 前言 电子邮件功能 日历和会议功能 联系人和任务管理功能 移动设备支持
  • pythonz字符串去重并排序

    项目场景 python练习题 问题描述 输入一个非空字符串 去除重复的字符后 从小到大排序输出为一个新字符串 原因分析 去重可以运用python中set数据类型的特性 然后将去重的set转为列表 再调用sort 函数进行排序即可 解决方案
  • 二维已经 OUT 了?3DPose 实现三维人体姿态识别真香

    作者 李秋键 出品 AI科技大本营 ID rgznai100 引言 人体姿态估计是计算机视觉领域很多研究工作的基础 也是研究的热点问题 在行为识别 人机交互 姿态跟踪等领域有着广泛的应用前景 按照人体姿态维度的差异 可以将人体姿态估计任务分
  • Windows FFmpeg 多张图片合并视频

    执行指令 ffmpeg f image2 i images d jpg vcodec libx264 r 25 b 200k test mp4 图片资源截图 重点 图片资源名称的命名规则
  • JDK8 新特性-----对象::new

    public class demo public static void main String args 第一种方式 ICar iCar1 new ICar Override public Car getCar String name I
  • pycharm 安装第三方库方法

    pycharm 使用anaconda库并添加其他库 andconda 中可以添加多个虚拟环境 在安装路径下envs 文件下可以查看 安装的第三方库在D Anaconda3 Lib site packages 文件夹下 在运行程序时 在缺少安
  • 【C#学习笔记】读access2007

    using System using System Data OleDb namespace ConsoleApplication class Program static void Main string args string strC
  • A1103 Integer Factorization (30 分)(将N表示为K个整数的P次方的和)(DFS)(难)

    The K P factorization of a positive integer N is to write N as the sum of the P th power of K positive integers You are
  • STM32——DAC数模转换实验

    一 数模转换原理 STM32的DAC模块是十二位数字输入 电压输出型的DAC DAC可以配置为8位或12位模式 也可以与DMA控制器配合使用 DAC工作在12位模式时 数据可以设置成左对齐或者右对齐 DAC模块有2个输出通道 每个通道都有单
  • backgroundImage加载图片报403解决方法

    加载图片时报403错误 可能是链接防盗链导致 解决方法 在html中加入
  • join方法介绍

    首先给出结论 t join 方法只会使调用该方法的线程进入t对象的等待池 并等待t线程执行完毕后才会被唤醒 并不影响同一时刻处在运行状态的其他线程 一 使用方式 join是Thread类的一个方法 启动线程后直接调用 例如 Thread t