編寫多線程程式離不開 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(); // 喚醒任一等待線程
}
}
關鍵規則:
synchronized 內呼叫。wait() 釋放鎖並進入對象等待集;notify() 將線程移至鎖等待集(Entry Set)。NEW → start() → RUNNABLE → Scheduler 分派 → RUNNING → sleep()/wait()/join()/I/O → BLOCKED/WAITING → 被喚醒/超時 → 回 RUNNABLE → run() 結束 → TERMINATED。
yield() 禮讓。wait()/notify() 進階機制wait(long timeout) 支援超時自動返回。notifyAll() 避免信號遺漏導致的活鎖。while (condition) wait() 而非 if。推薦 implements Runnable:
JDK 1.2 起棄用 stop()、suspend()、resume(),因破壞鎖一致性、導致資料損壞。正確停止方式:共享 volatile boolean stopFlag + interrupt()。
多線程「神奇」在於結果不可預測,卻可透過同步工具精準控制。熟練掌握上述 API 與模式,配合大量實戰演練,方能駕馭併發複雜度。