Java?Thread.join()方法使用詳細(xì)解析
前言
全面介紹Java 中Thread.join()方法的用法,包括其核心作用、重載方法、工作原理、使用場景及注意事項(xiàng),從基礎(chǔ)到進(jìn)階逐步拆解,結(jié)合可運(yùn)行示例幫你徹底理解。
一、核心定位
Thread.join() 是 Thread 類的實(shí)例方法,核心作用是實(shí)現(xiàn)線程間的同步(等待機(jī)制):當(dāng)一個(gè)線程(如主線程)調(diào)用另一個(gè)目標(biāo)線程(如子線程)的join()方法時(shí),調(diào)用線程會(huì)進(jìn)入阻塞狀態(tài),直到目標(biāo)線程執(zhí)行完畢(進(jìn)入 TERMINATED 狀態(tài))或等待超時(shí),才會(huì)繼續(xù)執(zhí)行自身后續(xù)代碼。
簡單說:A.join() 讓「當(dāng)前線程」等「線程 A」執(zhí)行完,再繼續(xù)自己的工作。
二、重載方法
Java 提供了 3 個(gè)重載的join()方法,滿足不同等待需求:
| 方法簽名 | 功能說明 |
|---|---|
void join() throws InterruptedException | 無參重載:無限等待,調(diào)用線程會(huì)一直阻塞,直到目標(biāo)線程完全執(zhí)行終止(不會(huì)主動(dòng)超時(shí)) |
void join(long millis) throws InterruptedException | 帶毫秒?yún)?shù):超時(shí)等待,調(diào)用線程最多阻塞millis毫秒(千分之一秒),超時(shí)后自動(dòng)喚醒繼續(xù)執(zhí)行 |
void join(long millis, int nanos) throws InterruptedException | 帶毫秒 + 納秒?yún)?shù):高精度超時(shí)等待,理論上支持納秒級精度,但實(shí)際受操作系統(tǒng)時(shí)鐘精度限制(一般無需使用,優(yōu)先用毫秒重載) |
關(guān)鍵說明
join(0)等價(jià)于join():表示「無限等待目標(biāo)線程終止」,并非等待 0 毫秒。- 納秒?yún)?shù)(
nanos)取值范圍:0-999999,超出該范圍會(huì)拋出IllegalArgumentException。 - 所有重載方法均會(huì)拋出
InterruptedException(受檢異常),必須捕獲或聲明拋出(因?yàn)榈却械木€程可能被其他線程中斷)。
三、工作原理
1. 核心邏輯
當(dāng)線程T1調(diào)用線程T2.join()時(shí),底層執(zhí)行流程如下:
T1(調(diào)用線程)會(huì)先判斷T2(目標(biāo)線程)是否還處于存活狀態(tài)(isAlive());- 若
T2仍存活,T1會(huì)調(diào)用Object.wait()方法(底層依賴 Object 的等待機(jī)制),進(jìn)入阻塞狀態(tài); - 當(dāng)
T2執(zhí)行完畢終止時(shí),JVM 會(huì)自動(dòng)調(diào)用T2的notifyAll()方法,喚醒所有等待在T2對象上的線程(即T1); - 若設(shè)置了超時(shí)時(shí)間,
T1在等待超時(shí)后會(huì)自動(dòng)喚醒,無需T2終止。
2. 與Thread.sleep()的核心區(qū)別
很多人會(huì)混淆join()和sleep(),二者的核心差異在于鎖釋放和使用場景:
| 特性 | Thread.join() | Thread.sleep(long millis) |
|---|---|---|
| 方法類型 | 實(shí)例方法(針對目標(biāo)線程) | 靜態(tài)方法(針對當(dāng)前線程) |
| 鎖處理 | 阻塞時(shí)會(huì)釋放當(dāng)前線程持有的對象鎖 | 阻塞時(shí)不釋放任何鎖資源 |
| 等待狀態(tài) | 無參:WAITING;帶參:TIMED_WAITING | TIMED_WAITING |
| 喚醒條件 | 目標(biāo)線程終止 / 等待超時(shí) / 被中斷 | 時(shí)間到達(dá) / 被中斷 |
| 核心場景 | 線程間同步(等待其他線程完成) | 當(dāng)前線程暫停指定時(shí)間 |
四、實(shí)戰(zhàn)示例
示例 1:無參join()- 主線程等待子線程完全執(zhí)行
public class JoinBasicDemo {
public static void main(String[] args) {
// 定義子線程:模擬耗時(shí)任務(wù)(打印1-5,每次間隔500ms)
Thread subThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
Thread.sleep(500); // 模擬任務(wù)耗時(shí)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程執(zhí)行中:i = " + i);
}
System.out.println("子線程執(zhí)行完畢!");
}, "子線程-Test");
System.out.println("主線程啟動(dòng)子線程...");
subThread.start(); // 啟動(dòng)子線程
try {
System.out.println("主線程開始等待子線程執(zhí)行完畢...");
subThread.join(); // 主線程阻塞,等待subThread執(zhí)行完
} catch (InterruptedException e) {
e.printStackTrace();
}
// 子線程執(zhí)行完后,主線程才會(huì)執(zhí)行這里
System.out.println("主線程繼續(xù)執(zhí)行,程序結(jié)束!");
}
}
運(yùn)行結(jié)果(順序固定)
plaintext
主線程啟動(dòng)子線程... 主線程開始等待子線程執(zhí)行完畢... 子線程執(zhí)行中:i = 1 子線程執(zhí)行中:i = 2 子線程執(zhí)行中:i = 3 子線程執(zhí)行中:i = 4 子線程執(zhí)行中:i = 5 子線程執(zhí)行完畢! 主線程繼續(xù)執(zhí)行,程序結(jié)束!
示例 2:帶超時(shí)join(long millis)- 主線程等待超時(shí)后繼續(xù)執(zhí)行
修改示例 1 中的join()調(diào)用,設(shè)置超時(shí)時(shí)間 1500ms(子線程總耗時(shí) 2500ms):
public class JoinTimeoutDemo {
public static void main(String[] args) {
Thread subThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程執(zhí)行中:i = " + i);
}
System.out.println("子線程執(zhí)行完畢!");
}, "子線程-Timeout");
System.out.println("主線程啟動(dòng)子線程...");
subThread.start();
try {
System.out.println("主線程開始等待子線程(超時(shí)1500ms)...");
subThread.join(1500); // 最多等待1500ms
} catch (InterruptedException e) {
e.printStackTrace();
}
// 1500ms后,主線程不再等待,直接執(zhí)行后續(xù)代碼
System.out.println("主線程等待超時(shí),繼續(xù)執(zhí)行自身邏輯!");
}
}
運(yùn)行結(jié)果(主線程超時(shí)后提前執(zhí)行)
plaintext
主線程啟動(dòng)子線程... 主線程開始等待子線程(超時(shí)1500ms)... 子線程執(zhí)行中:i = 1 子線程執(zhí)行中:i = 2 子線程執(zhí)行中:i = 3 主線程等待超時(shí),繼續(xù)執(zhí)行自身邏輯! 子線程執(zhí)行中:i = 4 子線程執(zhí)行中:i = 5 子線程執(zhí)行完畢!
示例 3:多個(gè)線程join()- 串行等待 vs 并行等待
場景 1:串行等待(主線程依次等待多個(gè)子線程,子線程串行執(zhí)行)
public class JoinSerialDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程t1執(zhí)行中:i = " + i);
}
}, "線程t1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程t2執(zhí)行中:i = " + i);
}
}, "線程t2");
t1.start();
t1.join(); // 主線程先等t1執(zhí)行完
t2.start();
t2.join(); // 再等t2執(zhí)行完
System.out.println("所有子線程執(zhí)行完畢,主線程結(jié)束!");
}
}
場景 2:并行等待(主線程同時(shí)等待多個(gè)已啟動(dòng)的子線程,子線程并行執(zhí)行)
public class JoinParallelDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程t1執(zhí)行中:i = " + i);
}
}, "線程t1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程t2執(zhí)行中:i = " + i);
}
}, "線程t2");
// 先同時(shí)啟動(dòng)兩個(gè)子線程
t1.start();
t2.start();
// 主線程同時(shí)等待兩個(gè)子線程(誰先執(zhí)行完誰先喚醒,最終等待耗時(shí)為較慢線程的執(zhí)行時(shí)間)
t1.join();
t2.join();
System.out.println("所有子線程執(zhí)行完畢,主線程結(jié)束!");
}
}
五、關(guān)鍵注意事項(xiàng)(避坑指南)
- 調(diào)用對象必須是已啟動(dòng)的線程:若對未啟動(dòng)(NEW 狀態(tài))的線程調(diào)用
join(),方法會(huì)直接返回,不會(huì)產(chǎn)生阻塞(因?yàn)?code>isAlive()返回 false,無需等待)。Thread t = new Thread(() -> {}); t.join(); // 無阻塞,直接執(zhí)行后續(xù)代碼(t未啟動(dòng)) - 異常必須處理:
join()拋出的InterruptedException是受檢異常,若不捕獲 / 聲明拋出,編譯報(bào)錯(cuò)。當(dāng)調(diào)用線程被中斷時(shí),會(huì)觸發(fā)該異常并清除中斷狀態(tài)。 join()不改變線程的啟動(dòng)狀態(tài):join()僅負(fù)責(zé)阻塞調(diào)用線程,不會(huì)自動(dòng)啟動(dòng)目標(biāo)線程,必須先調(diào)用targetThread.start(),再調(diào)用targetThread.join()。- 多個(gè)線程等待同一個(gè)目標(biāo)線程:若多個(gè)線程同時(shí)調(diào)用同一個(gè)目標(biāo)線程的
join()方法,當(dāng)目標(biāo)線程終止時(shí),所有等待線程會(huì)被 JVM 同時(shí)喚醒(底層notifyAll())。 - 與鎖結(jié)合時(shí)的釋放特性:若調(diào)用線程持有某個(gè)對象鎖,調(diào)用
join()后會(huì)釋放該鎖(因?yàn)榈讓诱{(diào)用wait()),而sleep()不會(huì)釋放鎖,這是并發(fā)編程中的關(guān)鍵差異。
六、底層源碼簡化解析(幫助理解)
以下是Thread.join()的核心源碼(JDK8),剔除了冗余邏輯,保留核心流程:
// 無參join() 等價(jià)于 join(0)
public final void join() throws InterruptedException {
join(0);
}
// 帶毫秒?yún)?shù)的核心實(shí)現(xiàn)
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");
}
// millis=0 表示無限等待
if (millis == 0) {
// 只要目標(biāo)線程還存活,調(diào)用線程就一直wait
while (isAlive()) {
wait(0);
}
} else {
// 超時(shí)等待邏輯:循環(huán)判斷,避免虛假喚醒
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
源碼關(guān)鍵點(diǎn)
join()是synchronized方法:鎖對象是目標(biāo)線程實(shí)例(保證線程安全)。- 循環(huán)判斷
isAlive():避免「虛假喚醒」(即使被意外喚醒,若目標(biāo)線程未終止,仍會(huì)繼續(xù)等待)。 - 依賴
Object.wait():底層是 Object 的等待 / 喚醒機(jī)制,JVM 在目標(biāo)線程終止時(shí)自動(dòng)調(diào)用notifyAll()。
七、總結(jié)
- 核心功能:
Thread.join()實(shí)現(xiàn)線程同步,讓調(diào)用線程阻塞等待目標(biāo)線程終止或超時(shí)。 - 核心重載:無參(無限等待)、
join(long millis)(毫秒超時(shí))、join(long millis, int nanos)(高精度超時(shí),極少使用)。 - 關(guān)鍵差異:與
sleep()的核心區(qū)別是「釋放對象鎖」和「使用場景」,join()用于線程同步,sleep()用于當(dāng)前線程暫停。 - 避坑要點(diǎn):先啟動(dòng)目標(biāo)線程再調(diào)用
join()、處理InterruptedException、區(qū)分串行 / 并行等待。 - 底層邏輯:基于
Object.wait()實(shí)現(xiàn),通過循環(huán)判斷isAlive()避免虛假喚醒,目標(biāo)線程終止時(shí)由 JVM 喚醒等待線程。
到此這篇關(guān)于Java Thread.join()方法使用的文章就介紹到這了,更多相關(guān)Java Thread.join()方法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中token的存儲(chǔ)和獲取實(shí)例代碼
關(guān)于java獲取微信Token驗(yàn)證的問題相信很多人都遇見過,尤其是對剛接觸微信開發(fā)的人來說確實(shí)有點(diǎn)棘手,下面這篇文章主要給大家介紹了關(guān)于Java中token存儲(chǔ)和獲取的相關(guān)資料,需要的朋友可以參考下2022-08-08
Spring Boot + Vue 基于 RSA 的用戶身份
RSA是一種非對稱加密算法,適用于用戶身份認(rèn)證加密,本文介紹了基于RSA的用戶身份認(rèn)證加密機(jī)制的實(shí)現(xiàn),包括前端Vue.js使用jsencrypt庫對用戶名密碼進(jìn)行加密,后端使用RSA私鑰解密驗(yàn)證用戶憑據(jù),感興趣的朋友跟隨小編一起看看吧2024-11-11
Java使用正則表達(dá)式實(shí)現(xiàn)找出數(shù)字功能示例
這篇文章主要介紹了Java使用正則表達(dá)式實(shí)現(xiàn)找出數(shù)字功能,結(jié)合實(shí)例形式分析了Java針對數(shù)字的匹配查找及非數(shù)字替換操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-03-03
Spring中的底層架構(gòu)核心概念類型轉(zhuǎn)換器詳解
這篇文章主要介紹了Spring中的底層架構(gòu)核心概念類型轉(zhuǎn)換器詳解,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12

