1. 程式人生 > >Thread.join方法的解析(轉)

Thread.join方法的解析(轉)

原文連結:https://www.cnblogs.com/huangzejun/p/7908898.html

 

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 父執行緒就可以繼續執行下去了。

 

超下來,說不定下次會看看,為別人引一點流,哈哈