java并发系列-Java中的Thread.join()方法

1 简介

这篇文章讨论Thread类的多个join()方法。了解下这些方法的细节,写几个例子。

就像wait()notify方法,join()也用于线程内同步。

关于wait()notify更多内容,查看这篇文章

2 Thread.join()方法

join方法定义在Thread类中:

public final void join() throws InterruptedException

Waits for this thread to die.

当调用一个线程的join()方法时,当前的线程会进入等待状态,直到被调用的线程终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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("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());
}

正常情况下会打印如下日志:

1
2
3
4
5
INFO: Thread Created
INFO: Invoking join
INFO: Thread Thread-1 started
INFO: Thread Thread-1 exiting
INFO: Returned from join

当被调用的线程中断时,join() 方法也会返回。这种情况下,被调用线程会抛InterruptedException

最后,如果被调用的线程已经结束或者没有启动,调用join()方法也会立即返回。

1
2
Thread t1 = new SampleThread(0);
t1.join(); //returns immediately

3 带有超时时间的Thread.join()方法

被调用线程被阻塞或者执行很久,join() 方法会一直处于等待状态。这会导致当前线程处于无响应状态。为了解决这种问题,我们可以使用 join() 的重载方法,它可以指定超时时间。

有两个这样的重载方法:

public final void join(long millis) throws InterruptedException

Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.

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

Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.

这样使用重载方法:

1
2
3
4
5
6
7
8
@Test
public void givenStartedThread_whenTimedJoinCalled_waitsUntilTimedout()
throws InterruptedException {
Thread t3 = new SampleThread(10);
t3.start();
t3.join(1000);
assertTrue(t3.isAlive());
}

上述例子中,当前线程最多花1秒钟等待线程t3完成,如果超过1秒钟还没有完成,join()方法会立即返回,把控制权交给当前线程。

带超时时间的join()方法,定时机制依赖于操作系统,所以超时时间不一定精确执行。

4 Thread.join()方法和同步

调用join方法,除了会一直等待到被调用线程结束,还有另外一个同步的副作用。会创建一个happens-before的关系。

All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

一个线程的所有动作都会在成功调用join()之前发生。

这里的意思是当线程t1调用t2.join(),t2做的所有改变当join()返回后都是可见的。但是,如果我们没有调用join()或者使用其他的同步方式,就不能保证其他线程发生的变化能被当前线程看到,即使其他线程已经完成。

因此,就算调用join()方法的时候,线程已经处于结束状态时而且会马上返回,我们在某些场景仍然需要调用一次。

看看下面不正确的同步例子:

1
2
3
4
5
6
SampleThread t4 = new SampleThread(10);
t4.start();
// not guaranteed to stop even if t4 finishes.
do {

} while (t4.processingCount > 0);

为了正确地让上述代码达到同步效果,需要在上面循环中增加一句 t4.join() 或者使用其他同步机制。

5 总结

这篇文章讨论了join()方法和它们的行为,并展示了使用它们的小例子。可以看到join()方法在线程内同步中非常有用。


本文为译文,作者通过翻译达到学习目的。 原文链接 | 原文源码链接 | 本站源码链接