本文分三个部分对Thread.join()进行分析:源码分析
2. join() 源码分析this
1 // 父线程 2 public class Parent { 3 public static void main(String[] args) { 4 // 建立child对象,此时child表示的线程处于NEW状态 5 Child child = new Child(); 6 // child表示的线程转换为RUNNABLE状态 7 child.start(); 8 // 等待child线程运行完再继续运行 9 child.join(); 10 } 11 }
1 // 子线程 2 public class Child extends Thread { 3 public void run() { 4 // ... 5 } 6 }
上面代码展现了两个类:Parent(父线程类),Child(子线程类)。线程
Parent.main()方法是程序的入口,经过 Child child = new Child(); 新建child子线程(此时 child子线程处于NEW状态);3d
而后调用child.start()(child子线程状态转换为RUNNABLE);rest
再调用child.join(),此时,Parent父线程会等待child子线程运行完再继续运行。code
下图是我总结的 Java 线程状态转换图:对象
让父线程等待子线程结束以后才能继续运行。blog
咱们来看看在 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.
当咱们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
如下是 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() 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;
其中
(1) 三个方法都被final修饰,没法被子类重写。
(2) join(long), join(long, long) 是synchronized method,同步的对象是当前线程实例。
(2) 无参版本和两个参数版本最终都调用了一个参数的版本。
(3) join() 和 join(0) 是等价的,表示一直等下去;join(非0)表示等待一段时间。
从源码能够看到 join(0) 调用了Object.wait(0),其中Object.wait(0) 会一直等待,直到被notify/中断才返回。
while(isAlive())是为了防止子线程伪唤醒(spurious wakeup),只要子线程没有TERMINATED的,父线程就须要继续等下去。
(4) join() 和 sleep() 同样,能够被中断(被中断时,会抛出 InterrupptedException 异常);不一样的是,join() 内部调用了 wait(),会出让锁,而 sleep() 会一直保持锁。
以本文开头的代码为例,咱们分析一下代码逻辑:
调用链:Parent.main() -> child.join() -> child.join(0) -> child.wait(0)(此时 Parent线程会得到 child 实例做为锁,其余线程能够进入 child.join() ,但不能够进入 child.join(0), 由于child.join(0)是同步方法)。
若是 child 线程是 Active,则调用 child.wait(0)(为了防止子线程 spurious wakeup, 须要将 wait(0) 放入 while(isAlive()) 循环中。
一旦 child 线程不为 Active (状态为 TERMINATED), child.notifyAll() 会被调用-> child.wait(0)返回 -> child.join(0)返回 -> child.join()返回 -> Parent.main()继续执行, 子线程会调用this.notify(),child.wait(0)会返回到child.join(0) ,child.join(0)会返回到 child.join(), child.join() 会返回到 Parent 父线程,Parent 父线程就能够继续运行下去了。
我以为网上不少文章的描述有歧义,下面挑选一些描述进行分析,也欢迎你们留言一块儿讨论。
a. 子线程结束以后,"会唤醒主线程",父线程从新获取cpu执行权,继续运行。
这里感谢kerwinX的留言,子线程结束后,子线程的this.notifyAll()会被调用,join()返回,父线程只要获取到锁和CPU,就能够继续运行下去了。
b. join() 将几个并行的线程"合并为一个单线程"执行。
我理解这个说法的意思,可是这样描述只会让读者更难理解。
在调用 join() 方法的程序中,原来的多个线程仍然多个线程,并无发生“合并为一个单线程”。真正发生的是调用 join() 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。
一点感想:技术人员写做技术文章时,最好尽可能避免使用过于口语化的词汇。
由于这种词汇歧义比较大,会让读者感到更加困惑或造成错误的理解。