Java 淺析 Thread.join()

概要

本文分三個部分對Thread.join()進行分析:源碼分析

1. join() 的示例和做用ui

2. join() 源碼分析this

3. 對網上其餘分析 join() 的文章提出疑問spa

 

1. join() 的示例和做用

1.1 示例

 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 線程狀態轉換圖:對象

Java-thread-state-transition

 

1.2 join() 的做用

讓父線程等待子線程結束以後才能繼續運行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.

         當咱們調用某個線程的這個方法時,這個方法會掛起調用線程,直到被調用線程結束執行,調用線程纔會繼續執行。

 

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() 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 父線程就能夠繼續運行下去了。

 

3. 對網上其餘分析 join() 的文章提出疑問

我以爲網上不少文章的描述有歧義,下面挑選一些描述進行分析,也歡迎你們留言一塊兒討論。

 

a. 子線程結束以後,"會喚醒主線程"線程從新獲取cpu執行權,繼續運行。

這裏感謝kerwinX的留言,子線程結束後,子線程的this.notifyAll()會被調用,join()返回,父線程只要獲取到鎖和CPU,就能夠繼續運行下去了。

 

 b. join() 將幾個並行的線程"合併爲一個單線程"執行。

我理解這個說法的意思,可是這樣描述只會讓讀者更難理解。

在調用 join() 方法的程序中,原來的多個線程仍然多個線程,並無發生「合併爲一個單線程」。真正發生的是調用 join() 的線程進入 TIMED_WAITING 狀態,等待 join() 所屬線程運行結束後再繼續運行。

 

一點感想:技術人員寫做技術文章時,最好儘可能避免使用過於口語化的詞彙。

由於這種詞彙歧義比較大,會讓讀者感到更加困惑或造成錯誤的理解。

相關文章
相關標籤/搜索