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

Java 線程核心方法與同步機制深度解析

     編輯:Java 相關知識

前言

編寫多線程程式離不開 run()、start()、wait()/notify()、sleep()、yield()、join() 等核心方法,以及關鍵字 synchronized。本文通過 12 個漸進式範例,深入剖析它們的行為差異與最佳實踐。

一、run() vs start()

public class ThreadTest extends Thread {
    @Override public void run() {
        for (int i = 0; i < 10; i++) System.out.print(" " + i);
    }
    public static void main(String[] args) {
        new ThreadTest().start();
        new ThreadTest().start();
    }
}
  • run():普通方法,直接呼叫會在當前線程同步執行。
  • start():啟動新線程,JVM 會在新線程中非同步呼叫 run()。
  • 規則:run() 必須 public void、無參數,否則無法被 Thread 機制識別。

二、synchronized 保護的是「共享數據」

錯誤示範:不同實例各自同步

Runnable r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
new Thread(r1).start();
new Thread(r2).start(); // 兩把不同的鎖,無法互斥

正確示範:同一實例共享鎖

Runnable r = new ThreadTest();
new Thread(r).start();
new Thread(r).start(); // 同一把鎖,輸出必然有序

同步代碼塊縮小粒度

public void run() {
    for (int k = 0; k < 5; k++) System.out.println(...); // 非同步區
    synchronized (this) { // 僅保護關鍵區
        for (int k = 0; k < 5; k++) System.out.println(...);
    }
}

三、sleep() 不釋放鎖

public synchronized void run() {
    for (int k = 0; k < 5; k++) {
        if (k == 2) Thread.sleep(5000); // 持有鎖睡眠 5 秒
        System.out.println(...);
    }
}

觀察可知:sleep 期間其他線程無法進入同步方法,證明鎖未釋放。

四、join() 等待線程終止

Thread t = new Thread(r);
t.start();
t.join(); // 主線程阻塞直到 t 結束
System.out.println(a); // 確保輸出 5

常用於主線程等待子任務完成、測試驗證等場景。

五、yield() 僅讓同優先級線程

t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
if (k == 5 && name.equals("t1")) Thread.yield(); // t2 仍搶不到 CPU

實驗證明:yield() 不會讓低優先級線程搶佔 CPU。

六、wait()/notify()/notifyAll() 協作模型

public synchronized void run() {
    if (shareVar == 0) {
        for (int i = 0; i < 10; i++) {
            shareVar++;
            if (shareVar == 5) this.wait(); // 釋放鎖,進入等待集
        }
    } else {
        System.out.println(Thread.currentThread().getName() + " shareVar=" + shareVar);
        this.notify(); // 喚醒任一等待線程
    }
}

關鍵規則:

  1. 必須在 synchronized 內呼叫。
  2. wait() 釋放鎖並進入對象等待集;notify() 將線程移至鎖等待集(Entry Set)。
  3. 喚醒後需重新獲取鎖才能繼續執行。
  4. JDK 不保證喚醒順序(非 FIFO)。

七、線程生命週期狀態圖

NEW → start() → RUNNABLE → Scheduler 分派 → RUNNING → sleep()/wait()/join()/I/O → BLOCKED/WAITING → 被喚醒/超時 → 回 RUNNABLE → run() 結束 → TERMINATED。

八、調度策略與優先級

  • 搶佔式調度:高優先級可搶佔低優先級。
  • 同優先級不搶佔(依賴 OS 時間片)。
  • 不可依賴時間片寫程式,需主動 yield() 禮讓。

九、wait()/notify() 進階機制

  • 等待集(Wait Set)vs 入口集(Entry Set)。
  • wait(long timeout) 支援超時自動返回。
  • notifyAll() 避免信號遺漏導致的活鎖。
  • 虛假喚醒:建議用 while (condition) wait() 而非 if。

十、實現 Runnable vs 繼承 Thread

推薦 implements Runnable:

  • 符合組合優於繼承原則。
  • 便於同一任務實例被多線程共享(示範共享鎖)。
  • Applet/舊版框架強制要求。

十一、棄用方法警示

JDK 1.2 起棄用 stop()、suspend()、resume(),因破壞鎖一致性、導致資料損壞。正確停止方式:共享 volatile boolean stopFlag + interrupt()。

總結

多線程「神奇」在於結果不可預測,卻可透過同步工具精準控制。熟練掌握上述 API 與模式,配合大量實戰演練,方能駕馭併發複雜度。

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