1.概述

join()是Thread类中的一个方法,它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。通常用于在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。本文将探索join方法的使用方式和使用原理。

2.join方法使用

2.1 join()示意图

2.1.1线程运行示意图

上述案例示意图中,主线程A入栈运行,运行时创建子线程B和线程C执行任务,B线程或C线程调用join方法,阻塞当前线程A,B或C线程执行完成,A线程才会继续执行。

2.1.2案例代码

public class ThreadB extends Thread {public void run() {System.out.println("线程:" + Thread.currentThread().getName() + "休眠:10s");try {TimeUnit.SECONDS.sleep(10);} catch (Exception e) {e.printStackTrace();}System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");}}public class ThreadC extends Thread {public void run() {System.out.println("线程:" + Thread.currentThread().getName() + "休眠:1s");try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");}}public class Test {public static void main(String[] args) throws InterruptedException {System.out.println("主线程线程A开始运行。。。");ThreadB threadB = new ThreadB();threadB.setName("ThreadB");threadB.start();ThreadC threadC = new ThreadC();threadC.setName("ThreadC");threadC.start();threadB.join();System.out.println("主线程线程A结束运行");}
}

运行结果如下:

方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x被销毁后再继续执行线程z后面的代码。

2.2 join方法原理

先看一下join()方法的源码,源码基于JDK 1.8,具体如下:

 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) {//判断线程是否存活,若为true,调用wait()方法阻塞while (isAlive()) {wait(0);}} else {//阻塞指定时间while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

由上述源码可知,join方法的底层实际上还是调用了wait()方法,通过wait()方法实现线程阻塞等待,那么这里有个问题,阻塞完成后,如何通知呢?我们知道调用wait阻塞的线程,需要notify()或者notifyAll来进行唤醒,这里并没有显式调用这两个方法,那是如何实现的呢?这里涉及一个知识:

在java中,Thread类线程执行完run()方法后,会自动执行notifyAll()方法。

具体的实现在HotSpot的源码中,有个ensure_join方法,大家感兴趣可查看。

2.3 注意事项

2.3.1 join方法被interrupt会异常

测试代码如下:

public class ThreadA extends Thread {@Overridepublic void run() {for (int i = 0; i < Integer.MAX_VALUE; i++) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(i);String s = stringBuffer.toString();}}
}public class ThreadB extends Thread {@SneakyThrows@Overridepublic void run() {ThreadA threadA = new ThreadA();threadA.start();threadA.setName("ThreadA");threadA.join();System.out.println(Thread.currentThread().getName() + "运行完成!");}}public class ThreadC extends Thread {private ThreadB threadB;public ThreadC(ThreadB threadB) {this.threadB = threadB;}@Overridepublic void run() {threadB.interrupt();System.out.println(Thread.currentThread().getName() + "运行结束!");}
}public class Test {public static void main(String[] args) {ThreadB threadB = new ThreadB();threadB.start();threadB.setName("ThreadB");try {TimeUnit.SECONDS.sleep(1);}catch (Exception e) {e.printStackTrace();}ThreadC threadC = new ThreadC(threadB);threadC.setName("ThreadC");threadC.start();System.out.println(Thread.currentThread().getName()+"运行结束");}
}

运行结果如下:

上述代码中,ThreadB 中创建了一个ThreadA执行,并让threadA.join(),又让线程ThreadC调用线程ThreadB的interrupt()方法来打断进程。由运行结果可知,方法join()与interrupt()相遇,会出现异常,而上述进程又未终止,说明ThreadA还在运行。

2.3.2 方法join(long)与sleep(long)的区别

由上述join方法的源码可知,在执行完wait(long)方法后,当前线程的锁被释放,那么其它线程就可以调用此线程中的同步方法。sleep(long)方法却不释放锁,也就意味着当线程调用sleep方法时,线程会被阻塞,其它线程只能等待获取锁。
测试代码如下:

public class ThreadA extends Thread {private ThreadB threadB;public ThreadA(ThreadB threadB) {this.threadB = threadB;}@Overridepublic void run() {try {synchronized (threadB) {threadB.start();Thread.sleep(6000L);System.out.println(Thread.currentThread().getName() + "结束运行!");}} catch (Exception e) {e.printStackTrace();}}
}public class ThreadB extends Thread {public void run() {System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());try {Thread.sleep(5000L);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());}public synchronized void print() {System.out.println(Thread.currentThread().getName() + "打印了时间:" + System.currentTimeMillis());}
}public class ThreadC extends Thread {private ThreadB threadB;public ThreadC(ThreadB threadB) {this.threadB = threadB;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "开始运行!");threadB.print();System.out.println(Thread.currentThread().getName() + "运行完成!");}
}public class Test {public static void main(String[] args) {ThreadB threadB = new ThreadB();threadB.setName("ThreadB");ThreadA threadA = new ThreadA(threadB);threadA.start();threadA.setName("ThreadA");try {Thread.sleep(1000L);ThreadC threadC = new ThreadC(threadB);threadC.setName("ThreadC");threadC.start();} catch (Exception e) {e.printStackTrace();}}
}

运行结果如下:

由上述代码可知,线程ThreadA调用sleep方法一直持有线程ThreadB对象的锁,时间达到6s,所以线程ThreadC只能在ThreadA时间达到6s后释放线程ThreadB的锁时,才可以调用ThreaB的同步方法print()。由上述现象可知,Thread.sleep(long)不释放锁。
修改上述ThreadA中的代码,验证join()方法释放锁,具体代码如下:

public class ThreadA extends Thread {private ThreadB threadB;public ThreadA(ThreadB threadB) {this.threadB = threadB;}@Overridepublic void run() {try {synchronized (threadB) {threadB.start();//Thread.sleep(6000L);threadB.join();System.out.println(Thread.currentThread().getName() + "结束运行!");}} catch (Exception e) {e.printStackTrace();}}
}

运行结果如下:

由上述运行结果可知,在线程ThreadA中调用ThreadB的join()方法,线程ThreadC在ThreadB未执行完成后,获取到了锁,执行了同步方法print(),说明join(long)方法会释放锁。

2.3.3 join()方法后的代码提前执行

案例代码如下:

public class ThreadA extends Thread {private ThreadB threadB;public ThreadA(ThreadB threadB) {this.threadB = threadB;}@Overridepublic void run() {try {synchronized (threadB) {System.out.println(Thread.currentThread().getName() + "开始运行!");Thread.sleep(5000L);System.out.println(Thread.currentThread().getName() + "结束运行!");}} catch (Exception e) {e.printStackTrace();}}
}public class ThreadB extends Thread {public synchronized void run() {System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());try {Thread.sleep(5000L);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());}
}public static void main(String[] args) {try {ThreadB threadB = new ThreadB();ThreadA threadAA = new ThreadA(threadB);threadAA.start();threadAA.setName("threadAA");threadB.setName("threadB");threadB.start();threadB.join(2000);System.out.println(Thread.currentThread().getName() + "结束运行!");} catch (Exception e) {e.printStackTrace();}}

运行结果如下:

出现上述情况的原因如下:

1.b.join(2000)方法先抢到threadB锁,然后将锁释放;
2.ThreadA抢到锁,打印:threadAA开始运行,并且sleep 5s;
3.ThreadA打印:threadAA运行结束,并释放锁;
4.此时join(2000)和ThreadB争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁后打印:main运行结束;
5.ThreadB抢到锁打印ThreadB开始运行;
6.5s后ThreadB打印:threadB运行结束。

2.3.4 Thread.currentThread().join()方法

若调用这个方法,当前线程将一直被阻塞,无法退出,开发中因谨慎使用。修改2.1.2节中测试代码为:

public static void main(String[] args) throws InterruptedException {System.out.println("主线程线程A开始运行。。。");ThreadB threadB = new ThreadB();threadB.setName("ThreadB");threadB.start();ThreadC threadC = new ThreadC();threadC.setName("ThreadC");threadC.start();//threadB.join();Thread.currentThread().join();System.out.println("主线程线程A结束运行");
}

出现运行结果如下:

3.小结

1.join()方法主要使用场景是一个线程需要等待另线程的运行结果,需要阻塞当前线程等待;
2.join()和join(long)的主要区别是:join(long)阻塞指定时间;join()一直阻塞,直至线程被销毁;
3.join() 方法被interrupt()会抛出异常,join()方法使用后会释放锁,sleep(long)方法却不释放锁;
4.Thread.currentThread().join()方法会一直阻塞线程。

4.参考文献

1.https://www.jb51.net/article/216689.htm
2.《JAVA多线程编程核心技术》-高洪岩著

JAVA多线程基础篇-join方法的使用相关推荐

  1. JAVA多线程基础篇-关键字synchronized

    1.概述 syncronized是JAVA多线程开发中一个重要的知识点,涉及到多线程开发,多多少少都使用过.那么syncronized底层是如何实现的?为什么加了它就能实现资源串行访问?本文将基于上述 ...

  2. java多线程中的join方法详解

    java多线程中的join方法详解 方法Join是干啥用的? 简单回答,同步,如何同步? 怎么实现的? 下面将逐个回答. 自从接触Java多线程,一直对Join理解不了.JDK是这样说的:join p ...

  3. Java多线程基础篇(02)-多线程的实现

    为什么80%的码农都做不了架构师?>>>    1.概要 JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Call ...

  4. JAVA多线程基础篇 4、可见性、有序性与Volatile

    文章目录 1. 可见性问题和有序性问题 # 2. 可见性问题的实验 2.1 volatile确保了可见性 3. 一个指令乱序的实验 总结 1. 可见性问题和有序性问题 在多线程开发中,可见性问题和有序 ...

  5. java多线程基础篇第一篇-JMM

    1.在开始多线程之前,我们先来聊聊计算机的缓存 计算机处理一个程序需要cpu处理器与存储设备的交互.但是在计算机发展的过程中,cpu处理器的处理速度不断提高,而存储设备的读写速度却没有得到与cpu同样 ...

  6. java多线程基础篇(二)java线程常见问题Thread Dump日志分析

    线程常见问题 CPU占用率很高,响应很慢 CPU占用率不高,但响应很慢 线程出现死锁的情况 CPU占用率不高,但响应很慢 有的时候我们会发现CPU占用率不高,系统日志也看不出问题,那么这种情况下,我们 ...

  7. java多线程基础篇第二篇-volidate关键字

    volidate关键字 转载于:https://www.cnblogs.com/code-star/p/11197708.html

  8. JAVA多线程设计模式篇 12、Thread-Specific Storage模式——给我个柜子

    文章目录 1. ThreadLocal的使用示例 2. ThreadLocal的使用场景 2.1 线程隔离的数据库连接与事务 2.2 线程隔离的session会话 总结 多线程环境中即然共用资源这么困 ...

  9. Java多线程干货系列(1):Java多线程基础

    转载自  Java多线程干货系列(1):Java多线程基础 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学 ...

最新文章

  1. centos vnc配置笔记
  2. 迎娶了校花的学霸,竟把日子过成了这个样子!
  3. LWIP裸机环境下实现TCP与UDP通讯(转)
  4. python keyboard模块_python3 安装 pykeyboard 模拟浏览器
  5. ora-03115:不支持的网络数据类型 oracle,Oracle10g新增DBMS_FILE_TRANSFER包(二)
  6. 1006 换个格式输出整数 (15 分)
  7. 控制項學習四(屬性與事件)
  8. Python 凭什么打败 Java、C/C++,成为机器学习的唯一语言?
  9. [转载] python选择排序二元选择_选择排序:简单选择排序(Simple Selection Sort)
  10. 特征选择的基本方法概述
  11. 部署AdminLTE
  12. 导出数据库设计文档的几种方案
  13. 【Python】数据处理之One-Hot编码
  14. linux文件分号意思,linux中的分号和,|和||的用法与区别
  15. 电子之TTL和CMOS门电路的区别
  16. 干货分享|Compare essay的写作方法
  17. 5-2 jmu-java-m05-自定义Judgeable接口 (10分)
  18. php生成海报像素低,TP5.1生成海报
  19. mysql proxies priv_Mysql 5.7.18 利用MySQL proxies_priv实现类似用户组管理
  20. 用JS实现PC端淘宝查看商品图片放大镜效果

热门文章

  1. halcon例子学习matching路牌
  2. mysql 增加超级用户_给MySQL添加超级用户权限
  3. sse php,在nginx下利用php配置SSE的正确方法
  4. Python高级特性
  5. Flyme支付安全保护,年前来一发~
  6. 【Jenkins相关基础学习】
  7. node升级版本、npm升级版本
  8. 【Ubuntu18.04安装时最简单合理的分区方法】
  9. mysql in子句_MySQL IN子句
  10. 51单片机考试内容补充