JAVA多线程基础篇-join方法的使用
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方法的使用相关推荐
- JAVA多线程基础篇-关键字synchronized
1.概述 syncronized是JAVA多线程开发中一个重要的知识点,涉及到多线程开发,多多少少都使用过.那么syncronized底层是如何实现的?为什么加了它就能实现资源串行访问?本文将基于上述 ...
- java多线程中的join方法详解
java多线程中的join方法详解 方法Join是干啥用的? 简单回答,同步,如何同步? 怎么实现的? 下面将逐个回答. 自从接触Java多线程,一直对Join理解不了.JDK是这样说的:join p ...
- Java多线程基础篇(02)-多线程的实现
为什么80%的码农都做不了架构师?>>> 1.概要 JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Call ...
- JAVA多线程基础篇 4、可见性、有序性与Volatile
文章目录 1. 可见性问题和有序性问题 # 2. 可见性问题的实验 2.1 volatile确保了可见性 3. 一个指令乱序的实验 总结 1. 可见性问题和有序性问题 在多线程开发中,可见性问题和有序 ...
- java多线程基础篇第一篇-JMM
1.在开始多线程之前,我们先来聊聊计算机的缓存 计算机处理一个程序需要cpu处理器与存储设备的交互.但是在计算机发展的过程中,cpu处理器的处理速度不断提高,而存储设备的读写速度却没有得到与cpu同样 ...
- java多线程基础篇(二)java线程常见问题Thread Dump日志分析
线程常见问题 CPU占用率很高,响应很慢 CPU占用率不高,但响应很慢 线程出现死锁的情况 CPU占用率不高,但响应很慢 有的时候我们会发现CPU占用率不高,系统日志也看不出问题,那么这种情况下,我们 ...
- java多线程基础篇第二篇-volidate关键字
volidate关键字 转载于:https://www.cnblogs.com/code-star/p/11197708.html
- JAVA多线程设计模式篇 12、Thread-Specific Storage模式——给我个柜子
文章目录 1. ThreadLocal的使用示例 2. ThreadLocal的使用场景 2.1 线程隔离的数据库连接与事务 2.2 线程隔离的session会话 总结 多线程环境中即然共用资源这么困 ...
- Java多线程干货系列(1):Java多线程基础
转载自 Java多线程干货系列(1):Java多线程基础 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学 ...
最新文章
- centos vnc配置笔记
- 迎娶了校花的学霸,竟把日子过成了这个样子!
- LWIP裸机环境下实现TCP与UDP通讯(转)
- python keyboard模块_python3 安装 pykeyboard 模拟浏览器
- ora-03115:不支持的网络数据类型 oracle,Oracle10g新增DBMS_FILE_TRANSFER包(二)
- 1006 换个格式输出整数 (15 分)
- 控制項學習四(屬性與事件)
- Python 凭什么打败 Java、C/C++,成为机器学习的唯一语言?
- [转载] python选择排序二元选择_选择排序:简单选择排序(Simple Selection Sort)
- 特征选择的基本方法概述
- 部署AdminLTE
- 导出数据库设计文档的几种方案
- 【Python】数据处理之One-Hot编码
- linux文件分号意思,linux中的分号和,|和||的用法与区别
- 电子之TTL和CMOS门电路的区别
- 干货分享|Compare essay的写作方法
- 5-2 jmu-java-m05-自定义Judgeable接口 (10分)
- php生成海报像素低,TP5.1生成海报
- mysql proxies priv_Mysql 5.7.18 利用MySQL proxies_priv实现类似用户组管理
- 用JS实现PC端淘宝查看商品图片放大镜效果