線程阻塞是並發編程的核心概念。本文系統講解 join()、sleep()、yield()、wait() 四種讓線程暫停執行的方法,並重點分析 join() 的實現原理與應用場景。
阻塞線程不佔用 CPU,調度器忽略它,直到條件滿足轉為就緒狀態,爭奪時間片才能繼續運行。
| 方法 | 所屬類 | 釋放鎖 | 典型場景 |
|---|---|---|---|
join() | Thread | 否 | 等待子線程結束,獲取其結果 |
sleep() | Thread | 否 | 定時暫停,不涉及同步 |
yield() | Thread | 否 | 主動讓出 CPU,重新競爭 |
wait() | Object | 是 | 同步塊中等待通知,配合 notify/notifyAll |
public final void join() throws InterruptedException { join(0); }
public final synchronized void join(long millis) throws InterruptedException
join():無限等待目標線程終止。join(long millis):限時等待,超時後繼續執行。public class ThreadJoinTest {
private static String str = null;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
System.out.println("進入 " + Thread.currentThread().getName() + " 線程");
Thread.sleep(1000);
str = "hello World";
System.out.println(Thread.currentThread().getName() + " 線程業務處理完畢");
} catch (InterruptedException e) { e.printStackTrace(); }
});
thread.start();
try {
// thread.join(); // 取消註釋觀察差異
System.out.println(Thread.currentThread().getName() + " 線程處理業務開始");
System.out.println("獲取 str 值:" + str);
} catch (Exception e) { e.printStackTrace(); }
}
}
未調用 join() 輸出:
main 線程處理業務開始 進入 Thread-0 線程 獲取 str 值:null Thread-0 線程業務處理完畢
調用 join() 輸出:
main 線程處理業務開始 進入 Thread-0 線程 Thread-0 線程業務處理完畢 獲取 str 值:hello World
調用 join(100) 輸出: 等待 100ms 不夠,str 仍為 null。
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");
if (millis == 0) {
while (isAlive()) wait(0);
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) break;
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
本質:調用線程在目標線程對象監視器上 wait(),目標線程終止前 JVM 自動調用 notifyAll() 喚醒等待者。
join() 是線程間同步的輕量級手段,適用於「主線程依賴子線程結果」的場景。理解其底層使用 wait/notify 機制,有助於排查死鎖與超時問題。