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() 所属线程运行结束后再继续运行。

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: java.lang.ClassNotFoundException是一个常见的错误,通常有几个可能的原因。首先,可能是项目中没有引入该类所属的jar包的坐标。你可以查看项目的pom文件,确认是否引入了该jar包的坐标,或者检查项目的Maven dependencies下是否有该class的jar包。\[1\]其次,可能是项目中多个maven坐标引入的jar包所依赖有所重叠,导致版本不一致产生冲突。你可以检查刚刚加入的maven坐标所依赖的jar包,与项目中其他maven坐标引入的jar包所依赖的jar包是否相同。\[1\]简单的解决办法是,将刚刚引入的jar包注释掉,然后复制Maven dependencies下所有的jar包名称到记事本文件中,再将刚刚引入的jar包解注释,再次复制所有的jar包名称到记事本文件中,最后使用代码比对工具比对两者所引入的jar包的区别,以查看是否有jar包依赖的冲突。\[1\]另外,有时候该错误可能是因为在部署路径下的lib文件夹中缺少相应的jar包。你可以进入到tomcat的部署路径下的lib文件夹,检查是否缺少相应的jar包。\[2\]最后,需要注意的是,该错误通常是在程序运行时找不到类,而不是在编译时找不到类。因此,即使在IDEA编译时没有报错,但在运行时仍然可能找不到对应的类。\[3\]希望这些解释能帮助你解决java.lang.ClassNotFoundException的问题。 #### 引用[.reference_title] - *1* [maven工程下 java.lang.ClassNotFoundException原因浅析](https://blog.csdn.net/farYang/article/details/53168233)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Maven项目下java.lang.ClassNotFoundException的解决方法](https://blog.csdn.net/zym2895756/article/details/78233688)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [maven依赖传统导致的 java.lang.NoClassDefFoundError和ClassNotFoundException产生原因以及解决方法](https://blog.csdn.net/qq_45171957/article/details/126899231)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值