join() 是做什么用的
join() 定义在 Thread 类里,作用是让当前线程等待另一个线程结束。谁调用了 t.join(),谁就会阻塞,直到线程 t 执行完毕才继续往下走。
容易搞混的一点是"谁等谁":t.join() 阻塞的不是 t 自己,而是执行这行代码所在的那个线程。
为什么需要 join()
典型场景:主线程启动一个子线程去做耗时运算,主线程处理完自己手头的事情后,需要用到子线程算出来的结果——这时就要用 join(),等子线程真正跑完再继续,否则可能拿到还没算完的结果。
用例子理解
写三个类来演示:CustomThread1 循环打印 5 次、每次间隔 1 秒;CustomThread 在 run() 里调用了 t1.join(),要等 t1 跑完才继续;JoinTestDemo 是入口。
class CustomThread1 extends Thread {
public CustomThread1() {
super("[CustomThread1] Thread");
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + " loop at " + i);
Thread.sleep(1000);
}
System.out.println(threadName + " end.");
} catch (InterruptedException e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}
class CustomThread extends Thread {
private final CustomThread1 t1;
public CustomThread(CustomThread1 t1) {
super("[CustomThread] Thread");
this.t1 = t1;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
try {
t1.join();
System.out.println(threadName + " end.");
} catch (InterruptedException e) {
System.out.println("Exception from " + threadName + ".run");
}
}
}
public class JoinTestDemo {
public static void main(String[] args) throws InterruptedException {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " start.");
CustomThread1 t1 = new CustomThread1();
CustomThread t = new CustomThread(t1);
t1.start();
Thread.sleep(2000);
t.start();
t.join(); // main thread waits for t to finish
System.out.println(threadName + " end!");
}
}
main 线程先启动 t1,睡 2 秒后再启动 t,最后调用 t.join() 等 t 结束。执行顺序大致是:
| 顺序 | 发生了什么 |
|---|---|
| 1 | main 启动,随后 sleep(2000) |
| 2 | t1 启动,开始循环打印 loop 0、1 |
| 3 | 2 秒后 main 唤醒,启动 t |
| 4 | t 调用 t1.join(),阻塞等待 t1 |
| 5 | t1 继续打印 loop 2、3、4,然后结束 |
| 6 | t1 结束,t 从 join() 处唤醒,打印 end 并结束 |
| 7 | t 结束,main 从 t.join() 处唤醒,打印 end! |
如果把最后的 t.join() 去掉,main 线程不会再等 t 结束——它在 Thread.sleep(2000) 之后就直接打印 end! 并退出,t 是否跑完跟 main 无关。这说明 join() 只影响调用它的那个线程,不会影响其他线程。
从源码看 join()
无参的 join() 调用的是 join(0):
public final void join() throws InterruptedException {
join(0);
}
join(long millis) 的核心逻辑是一个基于 wait() 的循环,只要目标线程还活着(isAlive()),调用者就一直等:
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); // wait indefinitely until this thread dies
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
millis == 0 表示一直等到线程死亡为止;线程结束时 JVM 会唤醒所有在等它的线程,join() 本质上就是"在目标线程对象上 wait,直到它死亡为止"。
有一点容易被忽略:如果线程对象已经创建但还没调用过 start(),此时调用它的 join() 不会阻塞,会直接往下执行——因为这时 isAlive() 返回 false。
小结
join() 让调用者线程等待目标线程结束,本质上是在目标线程对象上做 wait(),等目标线程死亡时被唤醒通知。它只影响调用它的那个线程,常用于"主线程需要等子线程算完结果再继续"的场景。
原文参考:http://blog.csdn.net/bzwm/article/details/3881392
维护记录
| 时间 | 修改内容 | 原因 |
|---|---|---|
| 2026-07-16 | 重写全文结构与措辞,修正代码块(原文代码块围栏未正确闭合、逐行断行严重);文件移动到 content/post/language/java/thread-join.md;categories 由 Inbox 改为 language;标题改为 “Java Thread.join() 原理与用法”;标签由 reprint 改为 java、thread、remix、AI-assisted |
原文排版错乱(代码块缺少正确闭合围栏,正文逐行断行),分类与标签不准确,且原本归在 development 目录,与站内 Java 内容惯例(language/java)不符 |