Java線程方法之從線程角色到實戰(zhàn)避坑指南
各線程控制方法的典型使用場景(深度詳解)
針對初學者的理解特點,我會對join()、sleep()、wait()/notify()、yield()、LockSupport.park()/unpark()這 5 個核心方法,按照適配場景、通用做法、實戰(zhàn)案例、避坑指南、小總結的維度逐一拆解,全程明確標注「當前線程」和「目標線程」,所有案例都是可直接運行的極簡代碼,保證一看就懂、一跑就通。
一、Thread.join ():等其他線程 “干完活” 再繼續(xù)
1. 適配場景(什么時候用?)
join()的核心是當前線程依賴其他線程(目標線程)的執(zhí)行結果,常見場景:
- 主線程(當前線程)需要子線程(目標線程)的計算結果、下載的文件、查詢的數(shù)據(jù)庫數(shù)據(jù)等;
- 多線程任務匯總(主線程作為當前線程,等待多個子線程(目標線程)處理完任務后統(tǒng)一匯總);
- 控制線程執(zhí)行順序(線程 A(當前線程)等線程 B(目標線程)執(zhí)行完后,再執(zhí)行自身邏輯)。
2. 通用做法(標準寫法)
- 先創(chuàng)建并啟動目標線程(被等待的線程);
- 在當前線程中調用目標線程的
join()方法; - 必須處理
InterruptedException(捕獲或聲明拋出); - 多線程場景下,當前線程逐個調用目標線程的
join()或用循環(huán)批量調用。
3. 實戰(zhàn)案例
案例 1:主線程依賴子線程的計算結果
明確線程角色:
- 當前線程:main 主線程(執(zhí)行
calcThread.join()的線程); - 目標線程:calcThread 子線程(被主線程等待的線程)。
public class JoinDemo1 {
// 共享變量存儲子線程的計算結果
private static int sum = 0;
public static void main(String[] args) throws InterruptedException {
// 目標線程:計算1-100的和
Thread calcThread = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println("目標線程(calcThread):計算完成,sum=" + sum);
});
// 啟動目標線程
calcThread.start();
// 當前線程(主線程)等待目標線程計算完成
calcThread.join();
// 當前線程(主線程)使用目標線程的結果
System.out.println("當前線程(主線程):拿到結果,sum=" + sum);
}
}
輸出結果:
目標線程(calcThread):計算完成,sum=5050
當前線程(主線程):拿到結果,sum=5050
案例 2:多線程任務匯總(3 個線程下載文件,主線程等全部完成)
明確線程角色:
- 當前線程:main 主線程(執(zhí)行
t1.join()/t2.join()/t3.join()的線程); - 目標線程:t1、t2、t3 下載線程(被主線程等待的線程)。
public class JoinDemo2 {
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建3個目標線程(下載線程)
Thread t1 = new Thread(() -> System.out.println("目標線程(t1):文件1下載完成"), "下載線程1");
Thread t2 = new Thread(() -> System.out.println("目標線程(t2):文件2下載完成"), "下載線程2");
Thread t3 = new Thread(() -> System.out.println("目標線程(t3):文件3下載完成"), "下載線程3");
// 啟動所有目標線程
t1.start();
t2.start();
t3.start();
// 當前線程(主線程)等待所有目標線程完成
t1.join();
t2.join();
t3.join();
// 當前線程(主線程)匯總結果
System.out.println("當前線程(主線程):所有文件下載完成,開始合并文件!");
}
}
輸出結果:
目標線程(t1):文件1下載完成
目標線程(t2):文件2下載完成
目標線程(t3):文件3下載完成
當前線程(主線程):所有文件下載完成,開始合并文件!
4. 避坑指南(初學者必看)
| 坑點 | 表現(xiàn) | 解決方案 |
|---|---|---|
忘記處理InterruptedException | 編譯報錯 | 要么用try-catch捕獲,要么在方法上聲明throws InterruptedException |
目標線程自身調用join()(比如 t 中調用t.join()) | 線程永遠阻塞(自己等自己完成) | 絕對避免這種寫法 |
多實例的join()導致 “偽等待” | 想讓線程全局互斥,卻用了不同實例的join() | 保證所有線程使用同一個實例,或用靜態(tài)方法 + 類鎖 |
依賴join(long millis)的精準超時 | 實際喚醒時間比指定時間長 | 僅把超時作為 “最大等待時間”,不依賴其做精準定時 |
5. 小總結
join()是 **“等待依賴” 的工具 **,核心記住:
- 當前線程:調用
join()的線程(主動發(fā)起等待的線程); - 目標線程:被調用
join()的線程(被等待的線程); - 誰調用
join(),誰就等;等的是目標線程完成,不是自己暫停。
二、Thread.sleep ():讓當前線程 “歇一會兒”
1. 適配場景(什么時候用?)
sleep()的核心是當前線程主動暫停指定時間,時間到自動喚醒,無目標線程(只是當前線程自身行為),常見場景:
- 模擬延遲(比如驗證碼倒計時、測試時模擬網(wǎng)絡延遲);
- 降低 CPU 占用(避免空循環(huán)導致 CPU 100% 占用);
- 簡單定時(比如每隔 1 秒打印一次日志,非精準場景)。
2. 通用做法(標準寫法)
- 當前線程調用
Thread.sleep(),用try-catch包裹(必須處理InterruptedException); - 指定合理的暫停時間(毫秒為單位);
- 不要依賴
sleep()做精準定時(系統(tǒng)調度會有誤差); - 同步塊中使用
sleep()時,明確其不釋放鎖的特性。
3. 實戰(zhàn)案例
案例 1:驗證碼倒計時(模擬 60 秒倒計時)
明確線程角色:
- 當前線程:main 主線程(執(zhí)行
Thread.sleep(1000)的線程); - 無目標線程(只是主線程自身暫停)。
public class SleepDemo1 {
public static void main(String[] args) {
// 驗證碼倒計時60秒
int count = 60;
while (count > 0) {
System.out.println("當前線程(主線程):驗證碼倒計時:" + count + "秒");
try {
// 當前線程(主線程)暫停1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
}
System.out.println("當前線程(主線程):驗證碼已過期,請重新獲?。?);
}
}
輸出結果:每秒打印一次倒計時,直到 60 秒結束。
案例 2:降低 CPU 占用(空循環(huán)加 sleep)
明確線程角色:
- 當前線程:monitorThread 監(jiān)控線程(執(zhí)行
Thread.sleep(5000)的線程); - 無目標線程。
public class SleepDemo2 {
public static void main(String[] args) {
// 模擬一個持續(xù)運行的監(jiān)控線程(當前線程)
Thread monitorThread = new Thread(() -> {
while (true) {
// 執(zhí)行監(jiān)控邏輯
System.out.println("當前線程(monitorThread):監(jiān)控系統(tǒng)運行中...");
try {
// 當前線程(monitorThread)每隔5秒監(jiān)控一次
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
// 被中斷時退出循環(huán)
break;
}
}
});
monitorThread.start();
}
}
效果:線程每隔 5 秒執(zhí)行一次,CPU 占用率幾乎為 0(如果不加 sleep,空循環(huán)會讓 CPU 核心占滿)。
4. 避坑指南(初學者必看)
| 坑點 | 表現(xiàn) | 解決方案 |
|---|---|---|
認為sleep()會釋放鎖 | 同步塊中調用sleep(),其他線程無法獲取鎖 | 記住:sleep()不釋放任何鎖,要釋放鎖用wait() |
用sleep()做精準定時 | 實際執(zhí)行時間比預期長 | 精準定時用ScheduledExecutorService,而非sleep() |
忽略sleep()的中斷異常 | 線程被中斷后,無法正常退出 | 在catch塊中處理中斷(比如退出循環(huán)) |
| 睡眠時間設為 0 | 等同于Thread.yield()(主動讓步),但不推薦 | 要讓步直接用yield(),不要用sleep(0) |
5. 小總結
sleep()是 **“自我暫停” 的工具 **,核心記住:
- 當前線程:執(zhí)行
sleep()的線程(自身暫停的線程); - 無目標線程(只是自己歇,和其他線程無關);
- 自己歇,不釋放鎖,時間到必醒;適合簡單延遲和降 CPU,不適合精準定時和線程協(xié)作。
三、Object.wait ()/notify ()/notifyAll ():線程間的 “對話工具”
1. 適配場景(什么時候用?)
wait()/notify()的核心是線程間的條件協(xié)作,涉及兩類線程:
- 等待線程(當前線程):調用
wait()的線程(因條件不滿足而等待的線程); - 喚醒線程(目標線程):調用
notify()/notifyAll()的線程(滿足條件后喚醒等待線程的線程)。
常見場景:
- 生產(chǎn)者消費者模式(生產(chǎn)者是喚醒線程,消費者是等待線程;反之亦然);
- 線程間條件等待(線程 A 是等待線程,線程 B 是喚醒線程);
- 任務隊列的消費(消費線程是等待線程,添加任務的線程是喚醒線程)。
2. 通用做法(標準寫法,記死這個模板!)
- 必須在同步塊 / 同步方法中調用(持有對象的鎖);
- 用
while循環(huán)檢查條件(防止虛假喚醒); - 等待線程(當前線程):
synchronized(鎖對象) { while(條件不滿足) { 鎖對象.wait(); } // 執(zhí)行操作 }; - 喚醒線程(目標線程):
synchronized(鎖對象) { // 改變條件 鎖對象.notifyAll(); }; - 優(yōu)先用
notifyAll()而非notify()(避免喚醒錯線程)。
3. 實戰(zhàn)案例
案例 1:經(jīng)典生產(chǎn)者消費者模式(隊列滿則生產(chǎn)者等,隊列空則消費者等)
明確線程角色:
- 等待線程(當前線程):隊列滿時的生產(chǎn)者線程、隊列空時的消費者線程;
- 喚醒線程(目標線程):消費后的消費者線程(喚醒生產(chǎn)者)、生產(chǎn)后的生產(chǎn)者線程(喚醒消費者)。
public class WaitNotifyDemo {
// 共享隊列(用數(shù)組模擬,容量為3)
private static final int[] QUEUE = new int[3];
// 隊列當前元素個數(shù)
private static int count = 0;
// 鎖對象(所有線程共用同一個鎖)
private static final Object LOCK = new Object();
// 生產(chǎn)者線程:生產(chǎn)產(chǎn)品(往隊列加元素)
static class Producer extends Thread {
@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 隊列滿,當前線程(生產(chǎn)者)成為等待線程,等待消費者喚醒
while (count == QUEUE.length) {
try {
System.out.println("當前線程(生產(chǎn)者):隊列滿,等待消費者消費...");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生產(chǎn)產(chǎn)品
QUEUE[count] = (int) (Math.random() * 100);
System.out.println("當前線程(生產(chǎn)者):生產(chǎn)" + QUEUE[count]);
count++;
// 當前線程(生產(chǎn)者)成為喚醒線程,喚醒等待的消費者
LOCK.notifyAll();
}
// 模擬生產(chǎn)間隔
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消費者線程:消費產(chǎn)品(從隊列取元素)
static class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 隊列空,當前線程(消費者)成為等待線程,等待生產(chǎn)者喚醒
while (count == 0) {
try {
System.out.println("當前線程(消費者):隊列空,等待生產(chǎn)者生產(chǎn)...");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消費產(chǎn)品
count--;
System.out.println("當前線程(消費者):消費" + QUEUE[count]);
// 當前線程(消費者)成為喚醒線程,喚醒等待的生產(chǎn)者
LOCK.notifyAll();
}
// 模擬消費間隔
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 啟動1個生產(chǎn)者、1個消費者
new Producer().start();
new Consumer().start();
}
}
輸出結果:
當前線程(生產(chǎn)者):生產(chǎn)88
當前線程(生產(chǎn)者):生產(chǎn)12
當前線程(生產(chǎn)者):生產(chǎn)56
當前線程(生產(chǎn)者):隊列滿,等待消費者消費...
當前線程(消費者):消費56
當前線程(生產(chǎn)者):生產(chǎn)34
當前線程(消費者):消費34
當前線程(消費者):消費12
當前線程(消費者):消費88
當前線程(消費者):隊列空,等待生產(chǎn)者生產(chǎn)...
當前線程(生產(chǎn)者):生產(chǎn)77
...
案例 2:線程等待初始化完成(子線程等主線程初始化完畢后執(zhí)行)
明確線程角色:
- 等待線程(當前線程):workThread 子線程(執(zhí)行
LOCK.wait()的線程); - 喚醒線程(目標線程):main 主線程(執(zhí)行
LOCK.notifyAll()的線程)。
public class WaitNotifyDemo2 {
// 初始化完成標記
private static boolean initDone = false;
// 鎖對象
private static final Object LOCK = new Object();
public static void main(String[] args) {
// 等待線程(當前線程):workThread子線程,等待初始化完成
Thread workThread = new Thread(() -> {
synchronized (LOCK) {
// 等待初始化完成
while (!initDone) {
try {
System.out.println("當前線程(workThread):等待初始化...");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("當前線程(workThread):初始化完成,開始工作!");
}
});
workThread.start();
// 喚醒線程(目標線程):主線程,執(zhí)行初始化并喚醒子線程
try {
Thread.sleep(2000); // 模擬初始化耗時
synchronized (LOCK) {
initDone = true;
System.out.println("當前線程(主線程):初始化完成,喚醒子線程!");
LOCK.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結果:
當前線程(workThread):等待初始化...
當前線程(主線程):初始化完成,喚醒子線程!
當前線程(workThread):初始化完成,開始工作!
4. 避坑指南(初學者必看,這部分最容易錯?。?/h3>
| 坑點 | 表現(xiàn) | 解決方案 |
|---|---|---|
不在同步塊中調用wait()/notify() | 拋出IllegalMonitorStateException | 必須先獲取鎖,再調用方法 |
用if代替while檢查條件 | 出現(xiàn)虛假喚醒(線程被喚醒后,條件依然不滿足) | 強制用while循環(huán)檢查條件 |
用notify()代替notifyAll() | 只喚醒一個線程,若該線程不滿足條件,其他線程永遠等待 | 優(yōu)先用notifyAll(),除非明確只有一個等待線程 |
| 喚醒后忘記修改條件 | 線程被喚醒后,條件依然不滿足,再次等待 | 喚醒前必須修改條件(比如initDone = true) |
| 鎖對象不唯一 | 不同線程用不同的鎖對象,無法通信 | 所有線程共用同一個鎖對象 |
5. 小總結
wait()/notify()是 **“線程對話” 的工具 **,核心記?。?/p>
- 等待線程(當前線程):調用
wait()的線程(因條件不滿足等待的線程); - 喚醒線程(目標線程):調用
notifyAll()的線程(滿足條件后喚醒的線程); - 在同步塊中用 while 檢查條件,用 notifyAll () 喚醒;是線程協(xié)作的基礎,生產(chǎn)者消費者模式的核心實現(xiàn)方式。
四、Thread.yield ():讓當前線程 “讓個座”
1. 適配場景(什么時候用?)
yield()的核心是當前線程主動放棄 CPU 執(zhí)行權,回到就緒態(tài),讓操作系統(tǒng)重新調度,無目標線程(只是當前線程的讓步行為),常見場景:
- 非核心任務讓步(比如后臺統(tǒng)計、日志收集等低優(yōu)先級任務,主動讓 CPU 給高優(yōu)先級的業(yè)務線程);
- 提升并發(fā)公平性(防止單個線程長期占用 CPU,導致其他線程饑餓);
- 測試場景(模擬線程調度的隨機性)。
2. 通用做法(標準寫法)
- 當前線程直接調用
Thread.yield()(無需處理異常); - 不依賴
yield()保證執(zhí)行順序(操作系統(tǒng)不一定采納讓步請求); - 僅在非核心任務中使用,不用于核心業(yè)務邏輯。
3. 實戰(zhàn)案例
案例:高優(yōu)先級業(yè)務線程與低優(yōu)先級統(tǒng)計線程(統(tǒng)計線程主動讓步)
明確線程角色:
- 當前線程:StatThread 統(tǒng)計線程(執(zhí)行
Thread.yield()的線程); - 無目標線程(只是主動給其他線程讓 CPU)。
public class YieldDemo {
// 業(yè)務線程(高優(yōu)先級,處理核心邏輯)
static class BusinessThread extends Thread {
public BusinessThread() {
// 設置高優(yōu)先級(1-10,默認5)
setPriority(Thread.MAX_PRIORITY);
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("業(yè)務線程:處理訂單" + i);
}
}
}
// 統(tǒng)計線程(低優(yōu)先級,后臺統(tǒng)計):當前線程,執(zhí)行yield()讓步
static class StatThread extends Thread {
public StatThread() {
// 設置低優(yōu)先級
setPriority(Thread.MIN_PRIORITY);
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 當前線程(StatThread)主動讓步,讓業(yè)務線程先執(zhí)行
Thread.yield();
System.out.println("當前線程(StatThread):統(tǒng)計訂單" + i);
}
}
}
public static void main(String[] args) {
new BusinessThread().start();
new StatThread().start();
}
}
輸出結果:業(yè)務線程的訂單處理會優(yōu)先打印,統(tǒng)計線程的打印會穿插在其中(具體順序由操作系統(tǒng)調度決定)。
4. 避坑指南(初學者必看)
| 坑點 | 表現(xiàn) | 解決方案 |
|---|---|---|
依賴yield()保證執(zhí)行順序 | 線程執(zhí)行順序混亂(操作系統(tǒng)可能忽略讓步請求) | 不要用yield()控制執(zhí)行順序,用join()或wait()/notify() |
認為yield()會讓線程阻塞 | 線程只是回到就緒態(tài),隨時可能被重新調度 | 記住:yield()不阻塞,只是 “讓個座” |
頻繁調用yield() | 降低程序執(zhí)行效率(頻繁調度線程) | 僅在非核心任務中偶爾調用 |
5. 小總結
yield()是 **“主動讓步” 的工具 **,核心記?。?/p>
- 當前線程:執(zhí)行
yield()的線程(主動讓 CPU 的線程); - 無目標線程(只是讓 CPU,不針對特定線程);
- 讓 CPU,不阻塞,操作系統(tǒng)不一定采納;適合提升并發(fā)公平性,不適合控制執(zhí)行順序。
五、LockSupport.park ()/unpark ():靈活的 “線程開關”
1. 適配場景(什么時候用?)
LockSupport的park()/unpark()涉及兩類線程:
- 阻塞線程(當前線程):調用
park()的線程(被阻塞的線程); - 喚醒線程(目標線程):調用
unpark(Thread t)的線程(喚醒指定線程的線程),unpark()的參數(shù)就是被喚醒的目標線程。
常見場景:
- 自定義同步工具(比如實現(xiàn)自己的
CountDownLatch、Semaphore,JUC 工具類底層都用它); - 解決
wait()/notify()的 “喚醒丟失” 問題(可先unpark再park); - 靈活的線程通信(精準喚醒指定線程)。
2. 通用做法(標準寫法)
- 阻塞線程(當前線程):調用
LockSupport.park()阻塞自身; - 喚醒線程(目標線程):調用
LockSupport.unpark(Thread t)喚醒指定的阻塞線程; - 檢查中斷狀態(tài):
park()被中斷后不會拋異常,需用Thread.interrupted()檢查并處理。
3. 實戰(zhàn)案例
案例 1:基本的 park/unpark(主線程喚醒子線程)
明確線程角色:
- 阻塞線程(當前線程):workThread 子線程(執(zhí)行
LockSupport.park()的線程); - 喚醒線程(目標線程):main 主線程(執(zhí)行
LockSupport.unpark(workThread)的線程); - 被喚醒的目標線程:workThread(
unpark()的參數(shù))。
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo1 {
public static void main(String[] args) {
// 阻塞線程(當前線程):workThread子線程
Thread workThread = new Thread(() -> {
System.out.println("當前線程(workThread):開始執(zhí)行,準備阻塞...");
// 當前線程(workThread)阻塞自身
LockSupport.park();
System.out.println("當前線程(workThread):被喚醒,繼續(xù)執(zhí)行!");
}, "工作線程");
workThread.start();
// 喚醒線程(主線程):延遲2秒后喚醒目標線程(workThread)
try {
Thread.sleep(2000);
System.out.println("當前線程(主線程):喚醒目標線程(workThread)!");
LockSupport.unpark(workThread);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結果:
當前線程(workThread):開始執(zhí)行,準備阻塞...
當前線程(主線程):喚醒目標線程(workThread)!
當前線程(workThread):被喚醒,繼續(xù)執(zhí)行!
案例 2:解決 “喚醒丟失” 問題(先 unpark 再 park)
明確線程角色:
- 阻塞線程(當前線程):workThread 子線程(執(zhí)行
LockSupport.park()的線程); - 喚醒線程(目標線程):main 主線程(執(zhí)行
LockSupport.unpark(workThread)的線程); - 被喚醒的目標線程:workThread。
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo2 {
public static void main(String[] args) {
// 阻塞線程(當前線程):workThread子線程
Thread workThread = new Thread(() -> {
// 先被unpark,再park(不會阻塞)
System.out.println("當前線程(workThread):準備阻塞...");
LockSupport.park();
System.out.println("當前線程(workThread):執(zhí)行完成!");
});
// 喚醒線程(主線程):先執(zhí)行unpark,喚醒目標線程(workThread)
LockSupport.unpark(workThread);
System.out.println("當前線程(主線程):先執(zhí)行unpark(),喚醒目標線程(workThread)");
// 啟動阻塞線程
workThread.start();
}
}
輸出結果:
當前線程(主線程):先執(zhí)行unpark(),喚醒目標線程(workThread)
當前線程(workThread):準備阻塞...
當前線程(workThread):執(zhí)行完成!
(如果是wait()/notify(),先 notify 再 wait 會導致線程永遠阻塞,而park()/unpark()不會)
4. 避坑指南(初學者必看)
| 坑點 | 表現(xiàn) | 解決方案 |
|---|---|---|
忽略park()的中斷狀態(tài) | 線程被中斷后,park()返回但不拋異常,線程繼續(xù)執(zhí)行 | 用Thread.interrupted()檢查中斷狀態(tài),處理中斷邏輯 |
認為unpark()可以多次生效 | 多次unpark()等同于一次(許可只能用一次) | 每次park()前確保只有一次unpark() |
過度使用park()/unpark() | 簡單場景下代碼復雜度高 | 簡單場景用wait()/notify(),復雜場景(自定義同步工具)再用park()/unpark() |
5. 小總結
LockSupport是 **“底層線程控制工具”**,核心記?。?/p>
- 阻塞線程(當前線程):執(zhí)行
park()的線程(被阻塞的線程); - 喚醒線程:執(zhí)行
unpark()的線程; - 被喚醒的目標線程:
unpark()的參數(shù)指定的線程; - 不依賴對象鎖,可先 unpark 再 park,精準喚醒指定線程;是 JUC 工具的基礎,初學者先掌握用法,后續(xù)學并發(fā)框架時會更易理解。
六、所有方法的核心對比表(含線程角色,初學者收藏)
| 方法 | 核心作用 | 當前線程 | 目標線程 | 釋放鎖? | 喚醒方式 |
|---|---|---|---|---|---|
join() | 等其他線程完成 | 調用join()的線程 | 被調用join()的線程 | 釋放目標線程對象的鎖 | 目標線程完成后自動喚醒 |
sleep() | 自我暫停指定時間 | 執(zhí)行sleep()的線程 | 無 | 不釋放 | 時間到自動喚醒 |
wait() | 條件不滿足時等待 | 調用wait()的線程 | 調用notifyAll()的線程 | 釋放鎖對象的鎖 | 其他線程喚醒 |
notify() | 喚醒等待的線程 | 調用notify()的線程 | 被喚醒的等待線程 | 不釋放(仍持有鎖) | 主動調用notify()/notifyAll() |
yield() | 主動讓步 CPU | 執(zhí)行yield()的線程 | 無 | 不釋放 | 操作系統(tǒng)重新調度 |
park() | 阻塞自身 | 執(zhí)行park()的線程 | 調用unpark()的線程 | 不釋放 | 其他線程調用unpark()或中斷 |
unpark() | 喚醒指定線程 | 執(zhí)行unpark()的線程 | unpark()參數(shù)指定的線程 | 不釋放 | 主動調用unpark() |
七、初學者終極總結
記準線程角色:
- 誰調用方法,誰大概率是當前線程;
- 方法參數(shù)或被 操作的線程,通常是目標線程;
- 無參數(shù)、無被 操作線程的方法(如
sleep()、yield()),一般無目標線程。
記準核心用途:
- 等別人結果用
join()(當前線程等目標線程); - 自己歇會兒用
sleep()(當前線程自我暫停,無目標線程); - 線程對話用
wait()/notify()(當前線程等待,目標線程喚醒); - 主動讓步用
yield()(當前線程讓 CPU,無目標線程); - 靈活控制用
park()/unpark()(當前線程阻塞,目標線程精準喚醒)。
- 等別人結果用
避坑核心點:
- 所有中斷異常都要處理;
wait()必須用 while 檢查條件;- 鎖對象必須唯一;
- 不依賴非精準的定時 / 調度。
通過上面的案例和明確的線程角色標注,你可以直接復制代碼運行,結合實際效果理解每個方法的使用場景,這比死記硬背更有效。
總結
到此這篇關于Java線程方法之從線程角色到實戰(zhàn)避坑的文章就介紹到這了,更多相關Java線程角色到實戰(zhàn)避坑內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java字符串駝峰與下?lián)Q線格式轉換如何實現(xiàn)
這篇文章主要介紹了Java字符串駝峰與下?lián)Q線格式轉換如何實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11
Java使用poi組件導出Excel格式數(shù)據(jù)
這篇文章主要介紹了Java使用poi組件導出Excel格式數(shù)據(jù),需要的朋友可以參考下2020-02-02
spring中實現(xiàn)容器加載完成后再執(zhí)行自己的方法
這篇文章主要介紹了spring中實現(xiàn)容器加載完成后再執(zhí)行自己的方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
java Spring 5 新特性函數(shù)式Web框架詳細介紹
正如昨天Juergen博客中所提到的,Spring 5.0的第二個里程碑是引入了一個新的函數(shù)式web框架。在這篇文章中,我們將給出關于這個框架的更多信息,,需要的朋友可以參考下2016-12-12
SpringBoot3中數(shù)據(jù)庫集成實踐詳解
項目工程中,集成數(shù)據(jù)庫實現(xiàn)對數(shù)據(jù)的增曬改查管理,是最基礎的能力,所以下面小編就來和大家講講SpringBoot3如何實現(xiàn)數(shù)據(jù)庫集成,需要的可以參考下2023-08-08
springboot接收前端參數(shù)的四種方式圖文詳解
Spring Boot可以通過多種方式接收前端傳遞的數(shù)據(jù),下面這篇文章主要給大家介紹了關于springboot接收前端參數(shù)的四種方式,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-11-11

