1. 程式人生 > 程式設計 >Java中Thread.join()的使用方法

Java中Thread.join()的使用方法

概要

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

1. join() 的示例和作用

2. join() 原始碼分析

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

1. join() 的示例和作用

1.1 示例

// 父執行緒
public class Parent {
 public static void main(String[] args) {
  // 建立child物件,此時child表示的執行緒處於NEW狀態
  Child child = new Child();
  // child表示的執行緒轉換為RUNNABLE狀態
  child.start();
  // 等待child執行緒執行完再繼續執行
  child.join();
 }
}
// 子執行緒
public class Child extends Thread {
 public void run() {
  // ...
 }
}

上面程式碼展示了兩個類:Parent(父執行緒類),Child(子執行緒類)。

Parent.main()方法是程式的入口,通過Child child = new Child(); 新建child子執行緒(此時 child子執行緒處於NEW狀態);

然後呼叫child.start()(child子執行緒狀態轉換為RUNNABLE);

再呼叫child.join(),此時,Parent父執行緒會等待child子執行緒執行完再繼續執行。

下圖是我總結的 Java 執行緒狀態轉換圖:

Java中Thread.join()的使用方法

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 mayhave a program that will begin initializing the resources it needs before proceeding with therest of the execution. We can run the initialization tasks as threads and wait for its finalizationbefore continuing with the rest of the program.For this purpose,we can use the join() method of the Thread class. When we call thismethod using a thread object,it suspends the execution of the calling thread until the objectcalled finishes its execution.

當我們呼叫某個執行緒的這個方法時,這個方法會掛起呼叫執行緒,直到被呼叫執行緒結束執行,呼叫執行緒才會繼續執行。

2. join() 原始碼分析

以下是 JDK 8 中 join() 的原始碼:

public final void join() throws InterruptedException {
 join(0);
}

public final synchronized void join(long millis)
throws InterruptedException {
 long base = System.currentTimeMillis();
 long now = 0;

 if (millis < 0) {
  throw new IllegalArgumentException("timeout value is negative");
 }

 if (millis == 0) {
  while (isAlive()) {
   wait(0);
  }
 } else {
  while (isAlive()) {
   long delay = millis - now;
   if (delay <= 0) {
    break;
   }
   wait(delay);
   now = System.currentTimeMillis() - base;
  }
 }
}

public final synchronized void join(long millis,int nanos)
throws InterruptedException {

 if (millis < 0) {
  throw new IllegalArgumentException("timeout value is negative");
 }

 if (nanos < 0 || nanos > 999999) {
  throw new IllegalArgumentException(
       "nanosecond timeout value out of range");
 }

 if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
  millis++;
 }

 join(millis);
}

join() 一共有三個過載版本,分別是無參、一個引數、兩個引數:

public final void join() throws InterruptedException;

public final synchronized void join(long millis) throws InterruptedException;

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() 所屬執行緒執行結束後再繼續執行。

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

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

到此這篇關於Java中Thread.join()的使用方法的文章就介紹到這了,更多相關Java Thread.join()內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!