Java?Thread中join方法使用舉例詳解
前言
join() 方法是 Java 并發(fā)編程中一個非常重要且基礎(chǔ)的方法,它允許一個線程等待另一個線程執(zhí)行完成。理解它的工作原理和使用場景對于編寫健壯的多線程應(yīng)用程序至關(guān)重要。
1.join()方法的定義和作用
join() 是 java.lang.Thread 類的一個實例方法。它的核心作用是阻塞當(dāng)前正在執(zhí)行的線程(我們稱之為“主線程”,盡管它可以是任何線程),直到調(diào)用 join() 方法的那個線程(我們稱之為“目標(biāo)線程”)執(zhí)行完畢。
簡單來說,如果線程 A 的代碼中調(diào)用了線程 B 的 join() 方法(即 B.join()),那么線程 A 就會進入等待狀態(tài),直到線程 B 執(zhí)行結(jié)束,線程 A 才會從 B.join() 這行代碼繼續(xù)往下執(zhí)行。
這就像在一個接力賽中,下一棒的選手(線程 A)必須等待上一棒的選手(線程 B)跑完自己的賽程并交接后,才能開始跑。
2.join()方法的三個重載版本
Thread 類提供了三個版本的 join() 方法:
public final void join()- 這是最常用的版本。它會一直等待,直到目標(biāo)線程執(zhí)行完畢。
- 它會拋出
InterruptedException,這意味著等待中的線程可能會被中斷。
public final synchronized void join(long millis)- 這是一個帶超時參數(shù)的版本。它會等待目標(biāo)線程執(zhí)行,但最多只等待指定的毫秒數(shù)。
- 如果目標(biāo)線程在指定的超時時間內(nèi)執(zhí)行完畢,當(dāng)前線程會立即繼續(xù)執(zhí)行。
- 如果超過了指定的毫秒數(shù),目標(biāo)線程還未執(zhí)行完,當(dāng)前線程也會停止等待,繼續(xù)執(zhí)行。
- 同樣會拋出
InterruptedException。
public final synchronized void join(long millis, int nanos)- 這個版本提供了更精確的超時控制,可以指定毫秒和納秒。
- 功能與
join(long millis)類似,只是提供了更高精度的時間控制。
3.join()方法的工作原理 (深入分析)
你可能會好奇,join() 是如何實現(xiàn)線程等待的?它的底層原理是什么?
isAlive()檢查: 當(dāng)你在線程 A 中調(diào)用threadB.join()時,join()方法內(nèi)部會首先檢查threadB是否還活著 (isAlive())。如果threadB已經(jīng)執(zhí)行完畢(或者從未啟動),isAlive()會返回false,那么join()方法會立即返回,線程 A 不會進行任何等待。wait()循環(huán)等待: 如果isAlive()返回true,join()方法會進入一個循環(huán)。在這個循環(huán)里,它會調(diào)用目標(biāo)線程對象(threadB)的wait()方法。- 關(guān)鍵點:
join()方法是一個synchronized方法。當(dāng)線程 A 調(diào)用threadB.join()時,它會獲取threadB這個對象的鎖。然后,join()方法內(nèi)部調(diào)用threadB.wait(),這會導(dǎo)致線程 A 釋放threadB的鎖,并進入threadB對象的等待集(Wait Set)中,狀態(tài)變?yōu)?WAITING或TIMED_WAITING。
- 關(guān)鍵點:
JVM 負責(zé)喚醒: 當(dāng)目標(biāo)線程
threadB的run()方法執(zhí)行完畢,準(zhǔn)備退出時,Java 虛擬機(JVM)會在其內(nèi)部自動調(diào)用threadB的notifyAll()方法。- 這個
notifyAll()會喚醒所有正在等待threadB對象鎖的線程,當(dāng)然也包括了之前因調(diào)用join()而等待的線程 A。
- 這個
重新獲取鎖并退出: 線程 A 被喚醒后,會嘗試重新獲取
threadB的對象鎖。一旦獲取成功,它會從wait()方法返回,并退出join()的循環(huán),從而結(jié)束等待,繼續(xù)執(zhí)行后續(xù)代碼。
總結(jié)一下核心機制: join() 的實現(xiàn)巧妙地利用了 Java 的內(nèi)置鎖(synchronized)和 wait()/notifyAll() 協(xié)作機制。它將線程間的執(zhí)行順序問題,轉(zhuǎn)化為了經(jīng)典的“生產(chǎn)者-消費者”模式中的線程同步問題。
4. 為什么要使用join()?使用場景
join() 的主要應(yīng)用場景是協(xié)調(diào)線程間的執(zhí)行順序。當(dāng)你需要確保一個或多個前置任務(wù)(在子線程中執(zhí)行)完成后,主線程才能繼續(xù)執(zhí)行后續(xù)任務(wù)時,join() 就非常有用。
常見場景:
- 數(shù)據(jù)初始化: 主線程啟動多個子線程去分別加載不同的資源或執(zhí)行初始化計算。主線程需要等待所有初始化工作完成后,才能使用這些資源去執(zhí)行核心業(yè)務(wù)邏輯。
- 分塊處理: 一個大任務(wù)被拆分成多個小任務(wù),每個小任務(wù)交給一個子線程處理。主線程需要等待所有子線程處理完畢后,對結(jié)果進行匯總和合并。
- 確保流程順序: 在一個復(fù)雜的業(yè)務(wù)流程中,步驟 B 必須在步驟 A 執(zhí)行完畢后才能開始。如果 A 和 B 在不同的線程中,那么 B 所在的線程就需要
join()A 所在的線程。
5. 代碼示例
示例 1: 等待單個線程
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開始運行...");
Thread workerThread = new Thread(() -> {
System.out.println("子線程開始工作...");
try {
// 模擬耗時操作
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程工作完成。");
});
workerThread.start(); // 啟動子線程
System.out.println("主線程調(diào)用 join() 等待子線程...");
workerThread.join(); // 關(guān)鍵:主線程在此處阻塞,等待 workerThread 執(zhí)行完畢
System.out.println("主線程在子線程完成后繼續(xù)運行。");
}
}
輸出:
主線程開始運行...
主線程調(diào)用 join() 等待子線程...
子線程開始工作...
// (等待約 2 秒)
子線程工作完成。
主線程在子線程完成后繼續(xù)運行。
示例 2: 等待多個線程
public class MultiJoinExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程開始...");
Thread t1 = new Thread(() -> {
System.out.println("線程1 開始處理...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("線程1 處理完畢。");
});
Thread t2 = new Thread(() -> {
System.out.println("線程2 開始處理...");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("線程2 處理完畢。");
});
// 啟動所有線程
t1.start();
t2.start();
System.out.println("主線程等待所有子線程完成...");
// 等待 t1 和 t2
t1.join();
t2.join();
System.out.println("所有子線程處理完畢,主線程繼續(xù)執(zhí)行。");
}
}
輸出:
主線程開始...
主線程等待所有子線程完成...
線程1 開始處理...
線程2 開始處理...
// (等待約 1 秒)
線程1 處理完畢。
// (再等待約 1 秒)
線程2 處理完畢。
所有子線程處理完畢,主線程繼續(xù)執(zhí)行。
6.InterruptedException的處理
join() 方法會拋出 InterruptedException。這是因為正在等待的線程(調(diào)用 join() 的線程)自身可能會被其他線程中斷 (interrupt())。
當(dāng)一個線程在 join() 等待期間被中斷時,join() 方法會立即拋出 InterruptedException。在 catch 塊中,通常需要處理這個中斷信號。一個好的實踐是重新設(shè)置當(dāng)前線程的中斷狀態(tài),以便上層調(diào)用者能夠知道發(fā)生了中斷。
try {
workerThread.join();
} catch (InterruptedException e) {
// 捕獲到中斷異常
System.out.println("主線程在等待期間被中斷了!");
// 恢復(fù)中斷狀態(tài),這是一種良好的編程實踐
Thread.currentThread().interrupt();
}
總結(jié)
| 特性 | 描述 |
|---|---|
| 目的 | 阻塞當(dāng)前線程,等待目標(biāo)線程執(zhí)行完畢。 |
| 核心原理 | 基于 synchronized 和 wait() / notifyAll() 機制。 |
| 版本 | join(), join(long millis), join(long millis, int nanos)。 |
| 使用場景 | 需要保證線程執(zhí)行順序,如等待子任務(wù)完成、數(shù)據(jù)初始化等。 |
| 異常 | 拋出 InterruptedException,需要妥善處理線程中斷。 |
| 關(guān)鍵點 | t.join() 是讓當(dāng)前線程等待 t 線程,而不是讓 t 線程等待。 |
到此這篇關(guān)于Java Thread中join方法使用的文章就介紹到這了,更多相關(guān)Java Thread中join方法使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis-Plus 處理邏輯刪除與查詢的5種核心實現(xiàn)方法
本文介紹了MyBatis-Plus在Java開發(fā)中實現(xiàn)邏輯刪除的5種方法,包括全局配置、實體類注解、局部配置、查詢過濾和多租戶結(jié)合,每種方法都有其適用場景和優(yōu)缺點,開發(fā)者可以根據(jù)項目需求選擇最合適的方案,感興趣的朋友跟隨小編一起看看吧2026-03-03
解決異常:Invalid?keystore?format,springboot配置ssl證書格式不合法問題
這篇文章主要介紹了解決異常:Invalid?keystore?format,springboot配置ssl證書格式不合法問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
java實現(xiàn)溫度單位轉(zhuǎn)換(攝氏度和華氏度)
在軟件開發(fā)中,溫度轉(zhuǎn)換是測量與控制系統(tǒng),氣象應(yīng)用,物聯(lián)網(wǎng)終端,科學(xué)計算等場景的基礎(chǔ)功能之一,所以本文將使用java實現(xiàn)溫度單位轉(zhuǎn)換功能,需要的可以了解下2025-07-07
結(jié)合mybatis-plus實現(xiàn)簡單不需要寫sql的多表查詢
這篇文章主要給大家介紹了關(guān)于結(jié)合mybatis-plus實現(xiàn)簡單不需要寫sql的多表查詢的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用mybatis-plus具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Java空指針異常NullPointerException的原因與解決方案
在Java開發(fā)中,NullPointerException(空指針異常)是最常見的運行時異常之一,通常發(fā)生在程序嘗試訪問或操作一個為null的對象引用時,這種異常不僅會導(dǎo)致程序崩潰,還會增加調(diào)試難度,所以本文系統(tǒng)梳理NullPointerException的成因、調(diào)試方法和避免策略2025-06-06
Java利用 Exchanger 實現(xiàn)游戲中交換裝備
JDK 1.5 開始 JUC 包下提供的 Exchanger 類可用于兩個線程之間交換信息。下面我們就來看看Java是如何利用Exchanger一行代碼實現(xiàn)游戲中交換裝備的2021-09-09

