首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 線程同步核心機制:wait/notify、sleep/join 深度解析與實戰模式

     編輯:Java 程式設計入門

前言:協作式並發的四大原語

Java 線程協作核心在於 `Object` 監視器方法 (`wait`/`notify`/`notifyAll`) 與 `Thread` 靜態/實例方法 (`sleep`/`join`)。理解它們的語義差異、鎖行為、喚醒條件,是編寫正確並發代碼的前提。

核心方法對比總覽

方法所屬釋放鎖喚醒條件典型用途
`wait()`Object✓ 完全釋放`notify()`/`notifyAll` / 中斷 / 超時條件等待、生產者消費者
`notify()`Object✗ (持有鎖退出同步區才釋放)隨機喚醒一個等待線程單消費者場景
`notifyAll()`Object✗喚醒所有等待線程多消費者、狀態變更廣播
`sleep(ms)`Thread (static)✗ 不釋放鎖時間到期節流、延遲、定時輪詢
`join()`Thread (instance)✗ 不釋放鎖目標線程終止主線程等待子線程完成

wait/notify:監視器條件等待模式

強制前提:必須在 synchronized 區塊內調用

synchronized (lock) {
    while (conditionNotMet) {  // 必須用 while 防止虛假喚醒
        lock.wait();           // 釋放鎖,進入 WAITING 狀態
    }
    // 條件滿足,執行業務邏輯
}

經典模式:生產者-消費者 (有界緩衝區)

class BoundedBuffer {
    private final Queue queue = new LinkedList<>();
    private final int capacity;
    
    public BoundedBuffer(int capacity) { this.capacity = capacity; }
    
    public synchronized void put(T item) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();  // 滿了等待
        }
        queue.offer(item);
        notifyAll();  // 喚醒消費者
    }
    
    public synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();  // 空了等待
        }
        T item = queue.poll();
        notifyAll();  // 喚醒生產者
        return item;
    }
}

關鍵最佳實踐

  • while 而非 if:防止虛假喚醒 (JVM 規範允許無通知喚醒)
  • notifyAll 優於 notify:避免單一消費者被喚醒但條件仍不滿足導致系統掛起
  • 狀態檢查與 wait 原子性:同步區包裹檢查+wait,防止競態條件
  • 生產環境優先用:`java.util.concurrent` (BlockingQueue、Condition、Lock) 而非手工 wait/notify

sleep:定時休眠不釋放鎖

// 靜態方法,作用於當前執行緒
Thread.sleep(1000);           // 休眠 1 秒
Thread.sleep(1000, 500000);   // 1 秒 500 萬納秒 (納秒部分四捨五入到毫秒)

// 實戰:輪詢間隔節流
while (!shutdown) {
    doWork();
    try { Thread.sleep(POLL_INTERVAL_MS); } 
    catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}

注意:`sleep` 不釋放任何鎖,若在 `synchronized` 區塊中睡眠,其他線程無法進入該同步區!

join:線程終止等待

本質實現

// Thread.join() 內部簡化邏輯
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    while (isAlive()) {
        long delay = millis - now;
        if (delay <= 0) break;
        wait(delay);  // 內部調用 Object.wait(timeout)
        now = System.currentTimeMillis() - base;
    }
}

即:`join()` = `while (thread.isAlive()) wait(timeout)`。目標線程終止時,JVM 會自動 `notifyAll()` 喚醒等待者。

三種重載

  • `join()`:無限期等待直到線程死亡
  • `join(long millis)`:最多等待指定毫秒
  • `join(long millis, int nanos)`:納秒精度 (最終仍折算毫秒)

實戰:主線程等待多個工作線程完成

List workers = Arrays.asList(t1, t2, t3);
workers.forEach(Thread::start);

for (Thread t : workers) {
    try { t.join(); } 
    catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
System.out.println("All workers finished.");

實戰對比場景:共用衛生間 (互斥鎖 + 條件等待)

場景一:sleep 輪詢 (低效、延遲不可控)

// B 等待 A 洗澡
while (a.isAlive()) {
    Thread.sleep(10000);  // 睡 10 秒醒來看看
    System.out.println("Waited " + (time += 10) + " min");
}

缺點:A 早洗完 B 仍睡到時間;A 晚洗完 B 反覆醒來檢查。

場景二:join 等待終止 (精確、無忙等)

// B 直接 join A
a.start();
a.join();  // 閻王鎖定:A 死前不放行
System.out.println("B can bath now!");

優點:A 結束瞬間 B 精確被喚醒,零延遲、零 CPU 浪費。

場景三:wait/notify 精細協作 (最靈活)

class Bathroom {
    private boolean occupied = false;
    private String currentUser;
    
    public synchronized void enter(String user) throws InterruptedException {
        while (occupied) wait();  // 等待空閒
        occupied = true;
        currentUser = user;
    }
    
    public synchronized void leave() {
        occupied = false;
        notifyAll();  // 通知等待者
    }
}

適用於:多條件、多狀態、需公平性/優先級的複雜協作。

現代並發包替代建議

傳統原語JUC 替代品優勢
synchronized + wait/notifyReentrantLock + Condition多條件變數、可中斷鎖、公平鎖、超時嘗試鎖
Thread.sleepTimeUnit.MILLISECONDS.sleep / ScheduledExecutorService可取消、精確調度、非阻塞延遲
Thread.joinCountDownLatch / CompletableFuture / ThreadPoolExecutor.awaitTermination多線程協調、異步組合、優雅關閉

總結

`wait/notify` 是監視器模式的核心,解決「條件滿足前等待」;`sleep` 是時間切片讓渡,不涉及協作;`join` 是線程生命週期同步的特例。掌握它們的**鎖語義**、**喚醒機制**、**虛假喚醒防護**,是並發編程基本功。生產代碼應優先拥抱 `java.util.concurrent` 高級抽象,將底層原語封裝在庫內部,業務代碼表達「做什麼」而非「怎麼同步」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved