浅析Thread.join()

JAVA学习网 2017-11-28 23:17:02

概要

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

1. join() 的示例和作用

2. join() 源码分析

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

 

1. join() 的示例和作用

1.1 示例

// 主线程
public class Parent extends Thread {
    public void run() {
        Child child = new Child();
        child .start();
        child .join();
        // ...
    }
}
// 子线程 public class Child extends Thread { public void run() { // ... } }

上面代码中有两个类:Parent(主线程类),Child(子线程类)。

在 Parent.run() 中,通过 new Child() 新建 child 子线程(此时 child 处于 NEW 状态),然后调用 child.start()(child 转换为 RUNNABLE 状态),再调用 child.join()。

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

 

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() 一共有三个版本:

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

其中

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

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

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

 

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

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

如果是 Active,则不断地调用 child.wait(0)(此时 Parent 会释放 child 实例锁,其他线程可以竞争锁并进入 child.join(0))。我们可以得知,Parent 线程在不断地对 child.wait(0) 入栈和出栈。

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

 

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

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

 

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

“唤醒”令人误解。其实是主线程调用方法不断去检查子线程的状态, 这是个主动的动作,而不是子线程去唤醒主线程

 

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

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

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

 

一点感想:技术人员写作技术文章时,如果没有能力驾驭中文词汇,就尽量避免使用生活中的词汇。不然最后只会让读者感到更加困惑或形成错误的理解。

 

阅读(774) 评论(0)