Java 內建多線程支援使併發編程相對簡單,但核心概念必須掌握。本文詳細解析Thread 繼承法、Runnable 介面法、synchronized 關鍵字三大基石,並說明 main() 也是一條線程。
Thread 為具體類,覆寫 run() 定義線程邏輯,調用 start() 啟動 (非直接調用 run())。
class TimePrinter extends Thread {
int pauseTime; String name;
public TimePrinter(int x, String n) { pauseTime = x; name = n; }
@Override public void run() {
while (true) {
try {
System.out.println(name + ": " + new Date());
Thread.sleep(pauseTime);
} catch (Exception e) { System.out.println(e); }
}
}
public static void main(String[] args) {
new TimePrinter(1000, "Fast Guy").start(); // 1秒輸出一次
new TimePrinter(3000, "Slow Guy").start(); // 3秒輸出一次
// main 線程繼續執行 (此處 main 結束但 JVM 存活因非守護線程)
}
}
輸出:兩條線程交替打印時間,間隔分別 1s/3s。
缺點:Java 單繼承限制,若類已有父類則無法使用;強加類層次結構。
實現 Runnable 介面 (run()),再包裝進 Thread 建構子。
class TimePrinter implements Runnable {
int pauseTime; String name;
public TimePrinter(int x, String n) { pauseTime = x; name = n; }
@Override public void run() { /* 同上 */ }
public static void main(String[] args) {
new Thread(new TimePrinter(1000, "Fast Guy")).start();
new Thread(new TimePrinter(3000, "Slow Guy")).start();
}
}
優點:
1. 避免單繼承限制,類可自由繼承其他父類。
2. 邏輯與線程控制分離,符合「組合優於繼承」。
3. 同一 Runnable 實例可提交給多個線程/線程池複用。
每個 Java 物件天生關聯一把監視器鎖。方法加 synchronized 即獲取該鎖,同一時間僅允許一線程執行該物件的同步方法。
public class Account {
String holderName; float amount;
public Account(String name, float amt) { holderName = name; amount = amt; }
// 非同步版:併發存取會導致金額錯誤
public void deposit(float amt) { amount += amt; }
public void withdraw(float amt) { amount -= amt; }
public float checkBalance() { return amount; }
}
併發場景:夫妻倆同時在不同 ATM 操作同一聯名賬戶,deposit/withdraw 交錯導致金額損壞。
public class Account {
String holderName; float amount;
public Account(String name, float amt) { holderName = name; amount = amt; }
public synchronized void deposit(float amt) { amount += amt; }
public synchronized void withdraw(float amt) { amount -= amt; }
public float checkBalance() { return amount; } // 唯讀無需同步
}
機制:deposit 與 withdraw 共用 this 鎖,互斥執行。checkBalance 無同步,不阻塞寫操作,寫操作也不阻塞它 (但可能讀到舊值,若需強一致性可加 synchronized 或用 volatile/AtomicReference)。
| 概念 | 說明 |
|---|---|
| Thread vs Runnable | Thread 繼承法簡單但受單繼承限制;Runnable 介面法靈活、可復用、推薦生產使用 |
| start() vs run() | start() 啟動新線程並異步調用 run();直接調用 run() 只是普通方法調用,不會開新線程 |
| main 線程 | JVM 啟動主線程執行 main(),用戶線程結束前 JVM 不退出 |
| 物件鎖 | 每個物件一把鎖,synchronized 實例方法鎖 this,靜態方法鎖 Class 物件 |
| 同步粒度 | 僅同步必要的共享狀態讀寫操作,避免過度同步降低併發度 |
AtomicInteger、ReentrantLock、CountDownLatch、ThreadPoolExecutor 等高級併發工具,優先於低級 synchronized/wait/notify。ConcurrentHashMap、CopyOnWriteArrayList 等替代手動同步。