Java中Thread类join方法的原理

用途

Thread.join() 方法表示线程让步,在 t1 线程中调用 t2 线程的 join() 方法,表示让 t1 线程进行等待,待 t2 线程执行完毕后,t1 线程继续执行。

源码解析

join 方法的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}

可以看到该方法由 synchronized 关键字修饰。synchronized 修饰一个方法,锁住的是当前实例对象。当在 t1 线程中调用 t2.join() 时,其实是将 t2 对象作为锁,t1 线程需要获取到 t2 对象的监视器锁(Monitor),才能执行 join() 方法中的逻辑。

进入 join() 方法后,根据时间参数执行不同的逻辑:

  • 如果时间参数小于 0,会抛出非法参数的异常。
  • 时间参数等于 0,不停检查 t2 线程是否存活,如果 t2 线程存活,则释放 t2 对象的监视器锁,挂起当前线程,即 t1 线程,t1 线程进入 WAITING 状态。
  • 时间参数大于 0,t1 线程每次挂起时,都会重新计算等待时间,直到等待时间小于等于0,或者线程 t2 结束。

当线程 t2 执行结束,JVM 底层自动触发 t2.notifyAll(),唤醒所有在 t2 对象上等待的线程,线程 t1 被唤醒后,重新获取 t2 对象的锁,while() 结束,join() 结束,继续执行 t2.join() 后的逻辑。

注意点

  • 阻塞的调用它的线程。比如 t1.join(),表示阻塞当前线程,而 t1 正常执行。
  • join() 方法必须在调用 start() 方法之后调用,如果在 start() 方法之前调用,由于 isAlive() 会直接 false,不会有等待的效果。
  • join(final long millis) 方法参数传 0 相当于不传参数,表示无限期等待,而不是等待 0 毫秒。