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 負(fù)責(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)文章
Go反射底層原理及數(shù)據(jù)結(jié)構(gòu)解析
這篇文章主要介紹了Go反射底層原理及數(shù)據(jù)結(jié)構(gòu)解析,反射的實現(xiàn)和interface的組成很相似,都是由“類型”和“數(shù)據(jù)值”構(gòu)成,下面小編分享更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-06-06深入淺析Mybatis與Hibernate的區(qū)別與用途
這篇文章主要介紹了Mybatis與Hibernate的區(qū)別與用途的相關(guān)資料,需要的朋友可以參考下2017-10-10java?socket實現(xiàn)局域網(wǎng)聊天
這篇文章主要為大家詳細(xì)介紹了java?socket實現(xiàn)局域網(wǎng)聊天,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05Spark調(diào)優(yōu)多線程并行處理任務(wù)實現(xiàn)方式
這篇文章主要介紹了Spark調(diào)優(yōu)多線程并行處理任務(wù)實現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08