Java 浅析 Thread.join()

概要

本文分为三部分对 Thread.join() 进行分析:

1. join() 的示例和作用

2. join() 源码分析

3. 对网上其他分析 join() 的文章提出疑问

 

1. join() 的示例和作用

1.1 示例

复制代码

1 // 父线程
2 public class Parent extends Thread {
3     public void run() {
4         Child child = new Child();
5         child.start();
6         child.join();
7         // ...
8     }
9 }

复制代码

复制代码

1 // 子线程
2 public class Child extends Thread {
3     public void run() {
4         // ...
5     }
6 }

复制代码

上面代码展示了两个类:Parent(父线程类),Child(子线程类)。

在 Parent.run() 中,通过 Child child = new Child(); 新建 child 子线程(此时 child 处于 NEW 状态);

然后再调用 child.start()(child 转换为 RUNNABLE 状态);

再调用 child.join()。

 

在 Parent 调用 child.join() 后,child 子线程正常运行,Parent 父线程会等待 child 子线程结束后再继续运行。

 

下图是我总结的 Java 线程状态转换图:

Java-thread-state-transition

 

1.2 join() 的作用

让父线程等待子线程结束之后才能继续运行

我们来看看在 Java 7 Concurrency Cookbook 中相关的描述(很清楚地说明了 join() 的作用):

Waiting for the finalization of a thread

In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

         当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

 

2. join() 源码分析

以下是 JDK 8 中 join() 的源码:

复制代码

 1 public final void join() throws InterruptedException {
 2     join(0);
 3 }
 4 
 5 public final synchronized void join(long millis)
 6 throws InterruptedException {
 7     long base = System.currentTimeMillis();
 8     long now = 0;
 9 
10     if (millis < 0) {
11         throw new IllegalArgumentException("timeout value is negative");
12     }
13 
14     if (millis == 0) {
15         while (isAlive()) {
16             wait(0);
17         }
18     } else {
19         while (isAlive()) {
20             long delay = millis - now;
21             if (delay <= 0) {
22                 break;
23             }
24             wait(delay);
25             now = System.currentTimeMillis() - base;
26         }
27     }
28 }
29 
30 public final synchronized void join(long millis, int nanos)
31 throws InterruptedException {
32 
33     if (millis < 0) {
34         throw new IllegalArgumentException("timeout value is negative");
35     }
36 
37     if (nanos < 0 || nanos > 999999) {
38         throw new IllegalArgumentException(
39                             "nanosecond timeout value out of range");
40     }
41 
42     if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
43         millis++;
44     }
45 
46     join(millis);
47 }

复制代码

我们可以看到 join() 一共有三个重载版本(都是 final method,无法被子类覆盖):

1 public final void join() throws InterruptedException;
2 
3 public final synchronized void join(long millis) throws InterruptedException;
4 
5 public final synchronized void join(long millis, int nanos) throws InterruptedException;

其中

a. join() 和 join(long millis, int nanos) 最后都调用了 join(long millis)。

b. 带参数的 join() 都是 synchronized method。

c. join() 调用了 join(0),从源码可以看到 join(0) 不断检查当前线程(join() 所属的线程实例,非调用线程)是否是 Active。

d. join() 和 sleep() 一样,都可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了 wait(),会出让锁,而 sleep() 会一直保持锁。

 

以本文开头的代码为例,我们分析一下代码逻辑:

Parent 调用 child.join(),child.join() 再调用 child.join(0) (此时 Parent 会获得 child 实例作为锁,其他线程可以进入 child.join() ,但不可以进入 child.join(0), 因为无法获取锁)。child.join(0) 会不断地检查 child 线程是否是 Active。

如果 child 线程是 Active,则循环调用 child.wait(0)(为了防止 Spurious wakeup, 需要将 wait(0) 放入 for 循环体中;此时 Parent 会释放 child 实例锁,其他线程可以竞争锁并进入 child.join(0)。我们可以得知,可以有多个线程等待某个线程执行完毕)。

一旦 child 线程不为 Active (状态为 TERMINATED), child.join(0) 会直接返回到 child.join(), child.join() 会直接返回到 Parent 父线程,Parent 父线程就可以继续运行下去了。

 

3. 对网上其他分析 join() 的文章提出疑问

我觉得网上很多文章的描述有歧义,下面挑选一些描述进行分析,也欢迎大家留言一起讨论。

 

a. 子线程结束之后,"会唤醒主线程",父线程重新获取cpu执行权,继续运行。

“唤醒”令人误解。并非子线程主动采取了措施去唤醒父线程。父线程重新运行,都是由底层的调度引起的。

 

 b. join() 将几个并行的线程"合并为一个单线程"执行。

我理解提这个说法的人的意思。但是这样描述只会让读者更难理解。

在调用 join() 方法的程序中,原来的多个线程仍然多个线程,并没有发生“合并为一个单线程”。真正发生的是调用 join() 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。

 

一点感想:技术人员写作技术文章时,最好尽量避免使用过于口语化的词汇。因为这种词汇歧义比较大,会让读者感到更加困惑或形成错误的理解。

展开阅读全文

没有更多推荐了,返回首页