Java Thread.join () 方法详细解析

目录

一、核心定位

二、重载方法

关键说明

三、工作原理

1. 核心逻辑

2. 与Thread.sleep()的核心区别

四、实战示例

示例 1:无参join() - 主线程等待子线程完全执行

运行结果(顺序固定)

示例 2:带超时join(long millis) - 主线程等待超时后继续执行

运行结果(主线程超时后提前执行)

示例 3:多个线程join() - 串行等待 vs 并行等待

场景 1:串行等待(主线程依次等待多个子线程,子线程串行执行)

场景 2:并行等待(主线程同时等待多个已启动的子线程,子线程并行执行)

五、关键注意事项(避坑指南)

六、底层源码简化解析(帮助理解)

源码关键点

七、总结


全面介绍Java 中Thread.join()方法的用法,包括其核心作用、重载方法、工作原理、使用场景及注意事项,从基础到进阶逐步拆解,结合可运行示例帮你彻底理解。

一、核心定位

Thread.join() 是 Thread 类的实例方法,核心作用是实现线程间的同步(等待机制):当一个线程(如主线程)调用另一个目标线程(如子线程)的join()方法时,调用线程会进入阻塞状态,直到目标线程执行完毕(进入 TERMINATED 状态)或等待超时,才会继续执行自身后续代码。

简单说:A.join() 让「当前线程」等「线程 A」执行完,再继续自己的工作。

二、重载方法

Java 提供了 3 个重载的join()方法,满足不同等待需求:

方法签名功能说明
void join() throws InterruptedException无参重载:无限等待,调用线程会一直阻塞,直到目标线程完全执行终止(不会主动超时)
void join(long millis) throws InterruptedException带毫秒参数:超时等待,调用线程最多阻塞millis毫秒(千分之一秒),超时后自动唤醒继续执行
void join(long millis, int nanos) throws InterruptedException带毫秒 + 纳秒参数:高精度超时等待,理论上支持纳秒级精度,但实际受操作系统时钟精度限制(一般无需使用,优先用毫秒重载)

关键说明

  1. join(0) 等价于 join():表示「无限等待目标线程终止」,并非等待 0 毫秒。
  2. 纳秒参数(nanos)取值范围:0-999999,超出该范围会抛出IllegalArgumentException
  3. 所有重载方法均会抛出InterruptedException(受检异常),必须捕获或声明抛出(因为等待中的线程可能被其他线程中断)。

三、工作原理

1. 核心逻辑

当线程T1调用线程T2.join()时,底层执行流程如下:

  1. T1(调用线程)会先判断T2(目标线程)是否还处于存活状态(isAlive());
  2. T2仍存活,T1会调用Object.wait()方法(底层依赖 Object 的等待机制),进入阻塞状态;
  3. T2执行完毕终止时,JVM 会自动调用T2notifyAll()方法,唤醒所有等待在T2对象上的线程(即T1);
  4. 若设置了超时时间,T1在等待超时后会自动唤醒,无需T2终止。

2. 与Thread.sleep()的核心区别

很多人会混淆join()sleep(),二者的核心差异在于锁释放使用场景

特性Thread.join()Thread.sleep(long millis)
方法类型实例方法(针对目标线程)静态方法(针对当前线程)
锁处理阻塞时会释放当前线程持有的对象锁阻塞时不释放任何锁资源
等待状态无参:WAITING;带参:TIMED_WAITINGTIMED_WAITING
唤醒条件目标线程终止 / 等待超时 / 被中断时间到达 / 被中断
核心场景线程间同步(等待其他线程完成)当前线程暂停指定时间

四、实战示例

示例 1:无参join() - 主线程等待子线程完全执行

public class JoinBasicDemo { public static void main(String[] args) { // 定义子线程:模拟耗时任务(打印1-5,每次间隔500ms) Thread subThread = new Thread(() -> { for (int i = 1; i <= 5; i++) { try { Thread.sleep(500); // 模拟任务耗时 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行中:i = " + i); } System.out.println("子线程执行完毕!"); }, "子线程-Test"); System.out.println("主线程启动子线程..."); subThread.start(); // 启动子线程 try { System.out.println("主线程开始等待子线程执行完毕..."); subThread.join(); // 主线程阻塞,等待subThread执行完 } catch (InterruptedException e) { e.printStackTrace(); } // 子线程执行完后,主线程才会执行这里 System.out.println("主线程继续执行,程序结束!"); } } 
运行结果(顺序固定)

plaintext

主线程启动子线程... 主线程开始等待子线程执行完毕... 子线程执行中:i = 1 子线程执行中:i = 2 子线程执行中:i = 3 子线程执行中:i = 4 子线程执行中:i = 5 子线程执行完毕! 主线程继续执行,程序结束! 

示例 2:带超时join(long millis) - 主线程等待超时后继续执行

修改示例 1 中的join()调用,设置超时时间 1500ms(子线程总耗时 2500ms):

public class JoinTimeoutDemo { public static void main(String[] args) { Thread subThread = new Thread(() -> { for (int i = 1; i <= 5; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行中:i = " + i); } System.out.println("子线程执行完毕!"); }, "子线程-Timeout"); System.out.println("主线程启动子线程..."); subThread.start(); try { System.out.println("主线程开始等待子线程(超时1500ms)..."); subThread.join(1500); // 最多等待1500ms } catch (InterruptedException e) { e.printStackTrace(); } // 1500ms后,主线程不再等待,直接执行后续代码 System.out.println("主线程等待超时,继续执行自身逻辑!"); } } 
运行结果(主线程超时后提前执行)

plaintext

主线程启动子线程... 主线程开始等待子线程(超时1500ms)... 子线程执行中:i = 1 子线程执行中:i = 2 子线程执行中:i = 3 主线程等待超时,继续执行自身逻辑! 子线程执行中:i = 4 子线程执行中:i = 5 子线程执行完毕! 

示例 3:多个线程join() - 串行等待 vs 并行等待

场景 1:串行等待(主线程依次等待多个子线程,子线程串行执行)
public class JoinSerialDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t1执行中:i = " + i); } }, "线程t1"); Thread t2 = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t2执行中:i = " + i); } }, "线程t2"); t1.start(); t1.join(); // 主线程先等t1执行完 t2.start(); t2.join(); // 再等t2执行完 System.out.println("所有子线程执行完毕,主线程结束!"); } } 
场景 2:并行等待(主线程同时等待多个已启动的子线程,子线程并行执行)
public class JoinParallelDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t1执行中:i = " + i); } }, "线程t1"); Thread t2 = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t2执行中:i = " + i); } }, "线程t2"); // 先同时启动两个子线程 t1.start(); t2.start(); // 主线程同时等待两个子线程(谁先执行完谁先唤醒,最终等待耗时为较慢线程的执行时间) t1.join(); t2.join(); System.out.println("所有子线程执行完毕,主线程结束!"); } } 

五、关键注意事项(避坑指南)

  1. 异常必须处理join()抛出的InterruptedException是受检异常,若不捕获 / 声明抛出,编译报错。当调用线程被中断时,会触发该异常并清除中断状态。
  2. join()不改变线程的启动状态join()仅负责阻塞调用线程,不会自动启动目标线程,必须先调用targetThread.start(),再调用targetThread.join()
  3. 多个线程等待同一个目标线程:若多个线程同时调用同一个目标线程的join()方法,当目标线程终止时,所有等待线程会被 JVM 同时唤醒(底层notifyAll())。
  4. 与锁结合时的释放特性:若调用线程持有某个对象锁,调用join()后会释放该锁(因为底层调用wait()),而sleep()不会释放锁,这是并发编程中的关键差异。

调用对象必须是已启动的线程:若对未启动(NEW 状态)的线程调用join(),方法会直接返回,不会产生阻塞(因为isAlive()返回 false,无需等待)。

Thread t = new Thread(() -> {}); t.join(); // 无阻塞,直接执行后续代码(t未启动) 

六、底层源码简化解析(帮助理解)

以下是Thread.join()的核心源码(JDK8),剔除了冗余逻辑,保留核心流程:

// 无参join() 等价于 join(0) 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"); } // millis=0 表示无限等待 if (millis == 0) { // 只要目标线程还存活,调用线程就一直wait while (isAlive()) { wait(0); } } else { // 超时等待逻辑:循环判断,避免虚假唤醒 while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } 

源码关键点

  1. join()synchronized方法:锁对象是目标线程实例(保证线程安全)。
  2. 循环判断isAlive():避免「虚假唤醒」(即使被意外唤醒,若目标线程未终止,仍会继续等待)。
  3. 依赖Object.wait():底层是 Object 的等待 / 唤醒机制,JVM 在目标线程终止时自动调用notifyAll()

七、总结

  1. 核心功能Thread.join()实现线程同步,让调用线程阻塞等待目标线程终止或超时。
  2. 核心重载:无参(无限等待)、join(long millis)(毫秒超时)、join(long millis, int nanos)(高精度超时,极少使用)。
  3. 关键差异:与sleep()的核心区别是「释放对象锁」和「使用场景」,join()用于线程同步,sleep()用于当前线程暂停。
  4. 避坑要点:先启动目标线程再调用join()、处理InterruptedException、区分串行 / 并行等待。
  5. 底层逻辑:基于Object.wait()实现,通过循环判断isAlive()避免虚假唤醒,目标线程终止时由 JVM 唤醒等待线程。

Read more

Python中一切皆对象:深入理解Python的对象模型

Python中一切皆对象:深入理解Python的对象模型

Python中一切皆对象:深入理解Python的对象模型 * 什么是"一切皆对象"? * Python对象的类型层次 * 1. 内置类型对象 * 2. 函数对象 * 3. 类对象和实例对象 * 4. 模块对象 * 对象行为的统一性 * 特殊方法:对象行为的背后 * 对象模型的实际应用 * 性能考虑 * 总结 Python以其"一切皆对象"的设计哲学而闻名,这种设计为语言带来了极大的灵活性和一致性。本文将深入探讨Python的对象模型,解释为什么说"Python中一切皆对象",并通过实例展示这一特性如何影响我们的编程方式。 什么是"一切皆对象"? 在Python中,从简单的数字、字符串到复杂的函数、类甚至模块,所有这些都是对象。这意味着它们都有: 1. 身份(identity):对象在内存中的唯一地址,可通过id()函数获取 2.

By Ne0inhk
Python从0到100(九十四):深度可分离卷积的深入解析及在OPPORTUNITY数据集上的实战

Python从0到100(九十四):深度可分离卷积的深入解析及在OPPORTUNITY数据集上的实战

前言:零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学业升学和工作就业的先行者! 【优惠信息】 • 新专栏订阅前500名享9.9元优惠 • 订阅量破500后价格上涨至19.9元 • 订阅本专栏可免费加入粉丝福利群,享受: - 所有问题解答 -专属福利领取 欢迎大家订阅专栏:零基础学Python:Python从0到100最新最全教程! 本文目录: * 一、深度可分离卷积的基础原理 * 1. 传统卷积的痛点 * 2. 深度可分离卷积的核心思路 * 二、深度可分离卷积的架构 * 1. 输入层 * 2. 深度可分离卷积模块 * 2.1 深度卷积(Depthwise Convolution) * 2.2 点卷积(Pointwise Convolution)

By Ne0inhk
Anaconda安装(2024最新版)

Anaconda安装(2024最新版)

安装新的anaconda需要卸载干净上一个版本的anaconda,不然可能会在新版本安装过程或者后续使用过程中出错,完全卸载干净anaconda的方法,可以参考我的博客! 第一步:下载anaconda安装包         官网:Anaconda | The Operating System for AI (不过官网是外网,这里推荐国内清华大学的镜像源,对于国内的网络友好,下载速度更快!) 清华镜像网:Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source MirrorIndex of /anaconda/archive/ | 清华大学开源软件镜像站,致力于为国内和校内用户提供高质量的开源软件镜像、Linux 镜像源服务,帮助用户更方便地获取开源软件。本镜像站由清华大学 TUNA 协会负责运行维护。https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/         这里有许多版本,博主这些选择下载最新版本的2024.06-1版本

By Ne0inhk
2026最新Python+AI入门指南:从零基础到实战落地,避开90%新手坑

2026最新Python+AI入门指南:从零基础到实战落地,避开90%新手坑

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 【前言】 哈喽,各位想入门AI的小伙伴!随着生成式AI、大模型应用的爆发,Python+AI已成为最热门的技术组合,无论应届生求职、职场人转型还是兴趣探索,掌握这门技能都能打开新赛道。但很多新手都会陷入“先学Python还是先学AI”“数学不好能不能学”“学完不会实战”的困境。 本文结合2026年AI技术趋势,用「知识点+核心代码+流程图+表格」的形式,从零基础打通Python+AI入门全链路,聚焦热门易上手方向,全程干货,新手可直接跟着练,老司机可查漏补缺~ 一、为什么2026年入门AI,首选Python? 很多新手会问:“学AI一定要用Python吗?Java、C++不行吗?” 答案是:不是不行,但Python是效率最高、门槛最低、生态最完善的选择,

By Ne0inhk