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

Java 多線程核心方法深度解析:run/start、synchronized、sleep、join、yield、wait/notify 全景指南

     編輯:Java 程式設計入門

前言:多線程方法的分類與核心契約

Java 多線程 API 分為線程生命周期控制、同步互斥、線程協作三大類。掌握每個方法的語義契約、鎖行為、異常處理、典型誤區,是寫出正確並發代碼的前提。

一、線程啟動:run() vs start()——最經典的面試陷阱

方法語義執行線程是否異步可重複調用
run()普通方法調用,執行任務邏輯當前線程 (主線程)同步阻塞可重複
start()啟動新線程,JVM 異步調用 run()新創建的線程異步並發不可重複 (IllegalThreadStateException)

// 錯誤:直接調用 run(),單線程順序執行
thread.run(); 

// 正確:調用 start(),真正並發
thread.start(); 

核心契約

  • run() 必須是 public void run()
  • 線程啟動後進入 Runnable (就緒) 狀態,由調度器決定何時運行
  • 同一 Thread 實例 只能 start() 一次

二、同步互斥:synchronized 關鍵字——鎖的核心機制

三種鎖定範圍


// 1. 同步實例方法:鎖對象 = this
public synchronized void instanceMethod() { ... }

// 2. 同步靜態方法:鎖對象 = Class 對象 (如 MyClass.class)
public static synchronized void staticMethod() { ... }

// 3. 同步代碼塊:鎖對象 = 任意對象 (推薦精細控制)
public void blockMethod() {
    synchronized (lockObject) { // lockObject 必須非 null
        // 臨界區
    }
}

經典錯誤:鎖對象不一致導致同步失效


// 錯誤示範:兩個線程持有不同鎖對象
Runnable r1 = new Task(); // 對象 A
Runnable r2 = new Task(); // 對象 B
new Thread(r1).start();
new Thread(r2).start();

// Task.run() 加了 synchronized,但鎖的是各自的 this,互不干擾!

// 正確:共享同一鎖對象
Runnable sharedTask = new Task(); // 單一對象
new Thread(sharedTask).start();
new Thread(sharedTask).start(); // 共用同一把鎖

鎖的重入性

同一線程可重複獲得同一把鎖 (計數器 +1),避免死鎖。釋放時計數器 -1,歸零才真正釋放。

三、線程休眠與讓步:sleep() vs yield()

特性sleep(millis)yield()
釋放鎖不釋放 (持有 synchronized 鎖時睡眠會阻塞其他線程)不釋放
時間控制指定毫秒/納秒不可控,僅提示調度器
目標線程當前線程當前線程
優先級影響無關僅讓同優先級線程有機會
異常InterruptedException (必須捕獲)無
典型用途定時任務、限流、模擬耗時極少用,測試/調試佔位

// sleep 正確姿勢:捕獲中斷異常,保留中斷狀態
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 重置中斷標誌
    // 處理中斷邏輯
}

四、線程等待:join()——等待另一線程終止

方法語義釋放鎖異常
join()當前線程阻塞,直到目標線程終止不釋放InterruptedException
join(millis)帶超時的等待,超時返回不拋異常不釋放InterruptedException
Thread t = new Thread(() -> { /* 耗時任務 */ });
t.start();
// 主線程做其他事...
t.join(); // 這裡阻塞等待 t 結束
// t 已終止,繼續執行

應用場景

  • 主線程聚合多線程計算結果
  • 啟動順序有依賴的服務初始化
  • 測試代碼等待異步操作完成

五、線程協作核心:wait/notify/notifyAll——Object 監視器方法

核心前提

必須在 synchronized 同步塊/方法內調用,否則拋 IllegalMonitorStateException。

狀態轉換模型

[運行中] ──wait()──► [等待集] ──notify()──► [鎖池] ──獲取鎖──► [運行中]
                    ▲         │
                    │         └─notifyAll()──► [鎖池] (所有等待線程)
                    │
                    └─超時/被中斷

方法語義對比

方法動作釋放鎖喚醒數量喚醒後狀態
wait()當前線程進入對象等待集完全釋放—等待喚醒
wait(timeout)帶超時等待完全釋放—超時自動進入鎖池
notify()隨機喚醒一個等待線程不釋放 (調用者持有鎖)1進入鎖池競爭鎖
notifyAll()喚醒所有等待線程不釋放全部進入鎖池競爭鎖

經典生產者-消費者模式


class Buffer {
    private final List queue = new ArrayList<>();
    private final int capacity = 10;
    
    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == capacity) {
            wait(); // 滿了等待,釋放鎖
        }
        queue.add(value);
        notifyAll(); // 喚醒消費者
    }
    
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 空了等待,釋放鎖
        }
        int val = queue.remove(0);
        notifyAll(); // 喚醒生產者
        return val;
    }
}

關鍵最佳實踐

  1. 永遠用 while 循環檢查條件,防止虛假喚醒 (spurious wakeup)
  2. 優先 notifyAll(),除非確定單一條件變量且所有等待者邏輯相同
  3. 釋放鎖前更新共享狀態,喚醒後重新檢查條件
  4. 避免在同步塊中調用耗時操作,縮小臨界區

六、高層抽象替代方案 (JDK 5+ 推薦)

底層 API高層替代 (java.util.concurrent)優勢
synchronizedReentrantLock, ReadWriteLock, StampedLock可中斷、公平鎖、條件變量、讀寫分離
wait/notifyCondition (Lock.newCondition())多條件隊列、精確喚醒、API 更清晰
Thread + RunnableExecutorService, CompletableFuture, ThreadPoolExecutor線程池管理、異步組合、異常傳播、資源控制
volatile/原子操作AtomicInteger, LongAdder, AtomicReference無鎖 CAS、高並發計數器、復合操作
joinFuture.get(), CompletableFuture.join()帶超時、異常處理、組合編排

七、避坑總結速查表

| 陷阱 | 後果 | 正確姿勢 | |------|------|----------| | 直接調用 `run()` | 偽並發,單線程執行 | 必須調用 `start()` | | `synchronized` 鎖不同對象 | 同步失效,數據競爭 | 共享同一鎖對象 (`this`/靜態類/顯式對象) | | `sleep()` 持有鎖睡覺 | 降低並發度,甚至死鎖 | 睡眠前釋放鎖,或用 `LockSupport.parkNanos()` | | `wait()` 不在 `synchronized` 中 | `IllegalMonitorStateException` | 必須在同步塊內調用 | | `notify()` 唤醒錯誤線程 | 業務邏輯錯誤、活鎖 | 條件變量用 `while` 循環檢查,優先 `notifyAll()` | | 忽略 `InterruptedException` | 中斷信號丟失,線程無法優雅停止 | 捕獲後 `Thread.currentThread().interrupt()` 保留狀態 | | 用 `yield()` 控制調度 | 不可靠,跨平台行為差異大 | 避免使用,改用優先級/線程池/鎖 |

總結

Java 多線程基礎 API 雖然底層,但理解其鎖語義、狀態機、協作契約是掌握 JUC 高級組件的基石。實際開發中:優先用線程池+CompletableFuture 處理異步任務,用 ReentrantLock+Condition 處理複雜同步,用原子類處理計數器。只有在極簡單場景或維護舊代碼時,才直接使用本文所述的基礎方法。

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