Java 中的 Thread.join() 方法

更新于 2025-12-29

1. 概述

在本教程中,我们将讨论 Thread 类中的不同 join() 方法。我们会深入探讨这些方法的细节,并提供一些示例代码。

wait()notify() 方法类似,join() 是另一种线程间同步机制。

你可以快速浏览这篇教程,了解更多关于 wait()notify() 的内容。

2. Thread.join() 方法

join() 方法定义在 Thread 类中:

public final void join() throws InterruptedException

等待该线程终止。

当我们对某个线程调用 join() 方法时,调用线程将进入等待状态,直到被引用的线程执行完毕。

我们可以通过以下代码观察这一行为:

class SampleThread extends Thread {
    public int processingCount = 0;

    SampleThread(int processingCount) {
        this.processingCount = processingCount;
        LOGGER.info("Thread Created");
    }

    @Override
    public void run() {
        LOGGER.info("Thread " + this.getName() + " started");
        while (processingCount > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                LOGGER.info("Thread " + this.getName() + " interrupted");
            }
            processingCount--;
            LOGGER.info("Inside Thread " + this.getName() + ", processingCount = " + processingCount);
        }
        LOGGER.info("Thread " + this.getName() + " exiting");
    }
}

@Test
public void givenStartedThread_whenJoinCalled_waitsTillCompletion() 
  throws InterruptedException {
    Thread t2 = new SampleThread(1);
    t2.start();
    LOGGER.info("Invoking join");
    t2.join();
    LOGGER.info("Returned from join");
    assertFalse(t2.isAlive());
}

执行上述代码后,我们应看到类似如下的输出:

[main] INFO: Thread Thread-1 Created
[main] INFO: Invoking join
[Thread-1] INFO: Thread Thread-1 started
[Thread-1] INFO: Inside Thread Thread-1, processingCount = 0
[Thread-1] INFO: Thread Thread-1 exiting
[main] INFO: Returned from join

如果被引用的线程在执行过程中被中断,join() 方法也可能提前返回。此时,该方法会抛出 InterruptedException

此外,如果被引用的线程已经终止,或者尚未启动,则调用 join() 方法会立即返回。

Thread t1 = new SampleThread(0);
t1.join();  // 立即返回

3. 带超时参数的 Thread.join() 方法

如果被引用的线程被阻塞或处理时间过长,普通的 join() 方法会一直等待下去。这可能导致调用线程失去响应。为了解决这个问题,我们可以使用带超时参数的 join() 方法重载版本。

join() 方法有两个带超时参数的重载形式:

  • public final void join(long millis) throws InterruptedException
    

    最多等待 millis 毫秒,直到该线程终止。若超时时间为 0,则表示无限期等待。

  • public final void join(long millis, int nanos) throws InterruptedException
    

    最多等待 millis 毫秒加上 nanos 纳秒,直到该线程终止。

我们可以如下使用带超时的 join()

@Test
public void givenStartedThread_whenTimedJoinCalled_waitsUntilTimedout()
  throws InterruptedException {
    Thread t3 = new SampleThread(10);
    t3.start();
    t3.join(1000);
    assertTrue(t3.isAlive());
}

在此示例中,调用线程最多等待约 1 秒钟,等待 t3 线程完成。如果 t3 在此期间未完成,join() 方法将返回控制权给调用线程。

需要注意的是,带超时的 join() 方法依赖于操作系统的定时机制,因此不能保证其等待时间完全精确。

4. Thread.join() 方法与同步

除了等待线程终止之外,调用 join() 方法还具有同步效果。join() 建立了一种 happens-before(先行发生) 关系:

“一个线程中的所有操作,在另一个线程成功从对该线程的 join() 调用返回之前,都对其可见。”

这意味着,当线程 t1 调用 t2.join() 后,t2 所做的所有修改对 t1 都是可见的。然而,如果我们不调用 join() 或使用其他同步机制,即使 t2 已经完成,也不能保证 t1 能看到 t2 所做的更改。

因此,即使对一个已终止线程调用 join() 会立即返回,在某些情况下我们仍然需要显式调用它。

下面是一个未正确同步的代码示例:

SampleThread t4 = new SampleThread(10);
t4.start();
// 即使 t4 已经结束,也不能保证循环会停止
do {
       
} while (t4.processingCount > 0);

为了正确同步上述代码,我们可以在循环中加入带超时的 t4.join() 调用,或使用其他同步机制(如 volatilesynchronized 等)。

5. 结论

join() 方法在实现线程间同步方面非常有用。本文讨论了 join() 方法的不同形式及其行为,并通过代码示例进行了说明。