本文將介紹Java常用線程調度方法及實現原理,包括sleep、wait¬ify、join、park&unpark。
線程方法
| 方法 | 説明 |
|---|---|
| start() | 用於啓動線程,讓線程進入就緒狀態 ;
RUNNABLE 多次調用拋 IllegalThreadStateException 異常 |
| run() | 線程運行時會調用 run() 方法執行具體邏輯 |
| join() | 當前線程由運行狀態轉為等待狀態 |
| getId() | 獲取線程ID |
| getName()/setName() | 獲取/設置線程名 |
| getPriority()/setPriority() | 獲取/設置優先級,優先級 1~10,默認 5,優先級僅起提示作用 |
| getState() | 獲取線程狀態 |
| interrupt() | 打斷線程 |
| isInterrupted() | 判斷是否被打斷,不會清除打斷標記 |
| static interrupted() | 判斷是否被打斷,會清除打斷標記 |
| isAlive() | 是否存活,即是否未運行完畢 |
| static currentThread() | 獲取當前正在運行的線程 |
| static sleep() | 線程由運行狀態轉為等待狀態;
TimeUnit 的 sleep() 比 Thread 的 sleep() 更具可讀性 |
| static yield() | 線程由運行狀態轉為就緒狀態 |
對於 interrupt()
- 處於等待狀態(sleep、wait、join)的線程被 interrupt() 後 ,會拋異常 InterruptedException,打斷標記為false(被清除);
- 處於等待狀態(park)的線程被 interrupt() 後,打斷標記為 true。park() 僅對打斷標記為 false 的線程生效;
- 處於運行狀態的線程被 interrupt() 後,線程仍正常執行,打斷標記為 true
Thread - sleep
正在運行的線程主動調用 Thread.sleep(long) 方法,由運行狀態轉為等待狀態。
public class Thread implements Runnable {
public static void sleep(long millis) throws InterruptedException {
if (millis < 0) {...}
long nanos = MILLISECONDS.toNanos(millis);
ThreadSleepEvent event = beforeSleep(nanos);
try {
if (currentThread() instanceof VirtualThread vthread) {
// 虛擬線程
vthread.sleepNanos(nanos);
} else {
// 最終調用本地方法 sleep0()
sleep0(nanos);
}
} finally {
afterSleep(event);
}
}
private static native void sleep0 (long nanos) throws InterruptedException;
}
object - wait & notify
正在運行的線程發現條件不滿足時,主動調用 obj.wait(),進入 obj 對象對應的 Monitor 對象的 WaitSet 等待,狀態轉為 WAITING。
當其他線程調用 notify()/notifyAll() 方法時,會喚醒 WaitSet 中的一個/全部線程,使其進入 EntryList 參與鎖的競爭。
public class Object {
public final void wait(long timeoutMillis) throws InterruptedException {
long comp = Blocker.begin();
try {
// 最終調用本地方法 wait0()
wait0(timeoutMillis);
} catch (InterruptedException e) {...} finally {...}
}
private final native void wait0(long timeoutMillis) throws InterruptedException;
}
thread - join
join() 體現的是保護性暫停模式(Guarded Suspension),join() 通過 wait() 實現等待。
保護性暫停模式:線程執行任務時,若不滿足條件則轉為等待狀態(保護、暫停),直到滿足條件。為防止虛假喚醒,線程每次被喚醒需要再次檢查條件是否滿足。
public class Thread implements Runnable {
// join(long) 的需要滿足的條件就是超過等待時間或者被等待線程結束
public final void join(long millis) throws InterruptedException {
if (millis < 0){...};
if (this instanceof VirtualThread vthread) {..}
// 核心代碼
synchronized (this) {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
// 等待
wait(delay);
} while (
// 被等待線程存活
isAlive() &&
// 等待時間未超過限制
(
// 計算當前線程繼續等待時間
delay = millis -
NANOSECONDS.toMillis(
System.nanoTime() - startTime
)
) > 0
);
}
} else {
// 無時限等待,直到被等待線程結束
while (isAlive()) {
wait(0);
}
}
}
}
}
LockSupport - park & unpark
每個線程都有自己的一個 Parker 對象,由三部分組成 _counter , _cond 和 _mutex 。
調用 LockSupport.park(),檢查 _counter,若為 0,則進入 _cond 條件變量等待,若為 1,則獲得互斥鎖 _mutex ,將 _counter 置為0。
調用 LockSupport.unpark(),檢查 _counter,將 _counter 置為1,喚醒條件變量 \_cond 中等待的線程。
- park() 和 unpark() 是 LockSupport 類的靜態方法,不需要和 Monitor 搭配使用
- unpark 可以在 park 之前執行
LockSupport 的 park() / unpark() 基於 Unsafe 的 park() / unpark(),Unsafe 提供了較為底層的操作內存、線程的方法。
public class LockSupport {
public static void park() {
if (Thread.currentThread().isVirtual()) {
// 虛擬線程
VirtualThreads.park();
} else {
// 調用Unsafe的park()
U.park(false, 0L);
}
}
private static final Unsafe U = Unsafe.getUnsafe();
}
方法辨析
sleep & wait
- sleep() 是 Thread 的靜態方法,wait() 是 Object 的方法;
- sleep() 不需要和 synchronized 搭配使用,wait() 需要和 synchronized 搭配使用;
- sleep() 不會釋放鎖對象,wait() 會釋放鎖對象。
wait & park
- notify() 必須在 wait() 之後執行,unpark() 可在 park() 之前執行;
- wait() 需要和 synchronized 搭配使用,park() 不需要和 synchronized 搭配使用;
- notify() / notifyAll() 只能喚醒隨機一個或者全部等待線程;unpark() 可以精確喚醒特定線程。
END
文章文檔:公眾號 字節幺零二四 回覆關鍵字可獲取本文文檔。
如果覺得本文對您有一點點幫助,歡迎點贊轉發,這會對我有非常大的幫助,咱們下期見!