Thread.join() 是 Java 多線程編程中實現「線程等待」的核心方法。它允許一個線程等待另一個線程執行結束後再繼續運行。本文通過源碼定義、實戰示例、輸出結果分析,透徹講解 join() 的工作原理、重載形式及常見應用場景。
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
InterruptedException:若當前線程在等待期間被中斷,會拋出此異常並清除中斷狀態。join() 內部實際上是調用 Object.wait() 實現的:
public final void join() throws InterruptedException {
while (isAlive()) {
wait(0); // 永久等待,直到被 notifyAll 喚醒(線程結束時 JVM 自動調用)
}
}
當線程結束時,JVM 會自動調用該線程對象的 notifyAll(),喚醒所有在 join() 中等待的線程。
public class JoinDemo {
public static void main(String[] args) throws Exception {
Runnable task = () -> {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " 執行任務步驟 " + i);
sleep(300);
}
};
Thread t1 = new Thread(task, "Thread-A");
Thread t2 = new Thread(task, "Thread-B");
System.out.println("主線程啟動 Thread-A");
t1.start();
System.out.println("主線程調用 t1.join(),等待 Thread-A 結束...");
t1.join(); // 主線程阻塞在此,直到 Thread-A 運行結束
System.out.println("Thread-A 已結束,主線程繼續啟動 Thread-B");
t2.start();
// 主線程繼續做自己的事
for (int i = 1; i <= 3; i++) {
System.out.println("主線程 正在處理其他業務 " + i);
sleep(200);
}
// 可選:等待 Thread-B 結束
t2.join();
System.out.println("所有子線程結束,主線程退出。");
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
主線程啟動 Thread-A
主線程調用 t1.join(),等待 Thread-A 結束...
Thread-A 執行任務步驟 1
Thread-A 執行任務步驟 2
Thread-A 執行任務步驟 3
Thread-A 執行任務步驟 4
Thread-A 執行任務步驟 5
Thread-A 已結束,主線程繼續啟動 Thread-B
Thread-B 執行任務步驟 1
主線程 正在處理其他業務 1
Thread-B 執行任務步驟 2
主線程 正在處理其他業務 2
Thread-B 執行任務步驟 3
主線程 正在處理其他業務 3
Thread-B 執行任務步驟 4
Thread-B 執行任務步驟 5
所有子線程結束,主線程退出。
關鍵觀察:
t1.join() 導致主線程完全阻塞,直到 Thread-A 運行完 5 個步驟才繼續。Thread-B 啟動後,主線程與 Thread-B 交替執行(併發),因為主線程未再調用 t2.join() 立即阻塞。t2.join() 確保主線程在退出前等待 Thread-B 結束。| 場景 | 說明 |
|---|---|
| 主線程等待初始化線程完成 | 應用啟動時,主線程啟動多個初始化線程(加載配置、連接數據庫、預熱緩存),隨後調用 join() 確保全部就緒再對外提供服務。 |
| 分階段任務處理 | 階段 1 的多線程任務必須全部完成,階段 2 才能開始(如 MapReduce 的 Map 階段結束前 Reduce 不啟動)。 |
| 測試代碼中等待異步操作 | 單元測試驗證異步回調、定時任務、事件總線發布後的處理結果時,常用 join() 或 CountDownLatch 等待。 |
| 優雅關閉應用 | 關閉鉤子中遍歷線程池或業務線程調用 join(timeout),給予有限時間完成收尾工作。 |
countDown(),主線程 await(),適合「等待 N 個任務全部完成」。allOf()、anyOf()、thenCompose() 等豐富組合,現代項目首選。threadB.join(),線程 B 又調用 threadA.join(),雙方永久等待 → 死鎖。join() 的線程若被 interrupt(),會拋出 InterruptedException,必須捕獲並重新設置中斷標誌(Thread.currentThread().interrupt()),否則上層調用者無法感知中斷。join(1000) 超時返回後,目標線程可能仍在運行,調用者需自行決定後續邏輯(繼續等待、放棄、記錄日誌等)。setDaemon(true)),主線程結束時 JVM 會強制終止它,join() 可能永遠等不到其「正常結束」。Thread.join() 是 Java 線程協作的基礎工具,通過 wait/notify 機制實現「等待目標線程終止」。理解其阻塞語義、超時行為、中斷處理,對編寫正確的多線程程序至關重要。實際工程中,根據複雜度選擇 join()、CountDownLatch 或 CompletableFuture,能讓併發控制代碼更清晰、更健壯。