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() 调用,或使用其他同步机制(如 volatile、synchronized 等)。
5. 结论
join() 方法在实现线程间同步方面非常有用。本文讨论了 join() 方法的不同形式及其行为,并通过代码示例进行了说明。