多線程是 Java 後端開發的核心竞争力,也是面試必考重災區。本文系統梳理線程生命週期、創建方式、屬性控制、狀態流轉、同步機制、線程間通信、優雅停止等核心知識點,並通過可運行代碼示例驗證關鍵行為,幫助你建立扎實的並發編程思維模型。
main 方法,JVM 內部至少運行:主線程、GC 線程、JIT 編譯線程、Reference Handler、Finalizer、Signal Dispatcher 等。Thread t = Thread.currentThread();
t.getName(); // 默認 "main" 或 "Thread-N"
t.getId(); // 唯一長整型 ID
t.getPriority(); // 1~10,默認 5 (NORM_PRIORITY)
t.getThreadGroup(); // 所屬線程組,樹狀結構
t.isDaemon(); // 是否守護線程
t.isInterrupted(); // 中斷標誌位
守護線程:JVM 啟動的非守護線程結束時,JVM 退出;守護線程不阻止 JVM 退出。典型:GC、JMX 監控線程。setDaemon(true) 必須在 start() 前調用。
| 方式 | 核心機制 | 「多對象多線程」 | 「單對象多線程」 | 優缺點 |
|---|---|---|---|---|
| 繼承 Thread | 子類重寫 run() | ✅ 每 new 一個子類實例即一線程 | ❌ 不支持 | 簡單直觀,但單繼承限制,邏輯與線程控制耦合 |
| 實現 Runnable | 傳入 Thread 構造器 | ✅ 每 new Runnable 實例 | ✅ 同一 Runnable 實例傳入多個 Thread | 解耦任務與線程,支援線程池,推薦方式 |
| 實現 Callable + FutureTask | 帶返回值、拋異常 | ✅ | ✅ | 異步計算場景,配合線程池 submit() |
class SharedTask implements Runnable {
private int counter = 0;
@Override public void run() {
for (int i = 0; i < 5; i++) {
synchronized (this) { // 共享 this 鎖
System.out.println(Thread.currentThread().getName() + " counter=" + ++counter);
}
}
}
}
public static void main(String[] args) {
SharedTask task = new SharedTask(); // 單一任務實例
new Thread(task, "T1").start();
new Thread(task, "T2").start();
new Thread(task, "T3").start();
}
輸出證明三線程共享 counter,需 synchronized(this) 保護。
(此處建議補充標準 6 狀態圖)
new Thread(),未調用 start()。start() 後,就緒/運行合併,等待 CPU 時間片。synchronized 鎖(非 java.util.concurrent 鎖)。Object.wait()、Thread.join()、LockSupport.park(),無超時。Thread.sleep()、wait(timeout)、join(timeout)、parkNanos。run() 正常返回或拋出未捕獲異常。關鍵點:sleep() 不釋放鎖;wait() 釋放鎖並進入 WAITING;yield() 僅讓出 CPU,狀態仍 RUNNABLE。
final Object lock = new Object();
boolean ready = false;
// 消費者
new Thread(() -> {
synchronized (lock) {
while (!ready) {
try { lock.wait(); } catch (InterruptedException ignored) {}
}
System.out.println("消費者收到通知,開始處理");
}
}, "Consumer").start();
// 生產者
Thread.sleep(1000);
synchronized (lock) {
ready = true;
lock.notifyAll(); // 喚醒所有等待者
}
規範:wait/notify 必須在同一鎖對象的 synchronized 塊內;用 while 而非 if 防止虛假喚醒。
Thread t = new Thread(() -> { /* 耗時任務 */ });
t.start();
// 主線程做其他事...
t.join(); // 阻塞直到 t 終止
System.out.println("子線程已結束");
重載 join(millis) 支援超時,避免無限等待。
適用於生產者-消費者字節流場景,內部基於環形緩衝區(默認 1KB),非高性能方案,高吞吐請用 Disruptor 或 BlockingQueue。
Thread.stop() 會釋放所有鎖導致數據不一致,suspend() 易死鎖,均已棄用。
class Worker implements Runnable {
@Override public void run() {
while (!Thread.currentThread().isInterrupted()) { // 輪詢中斷標誌
try {
doWork();
Thread.sleep(100); // 睡眠可響應 interrupt
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置標誌
break; // 退出循環
}
}
cleanup(); // 釋放資源
}
}
// 調用方
Thread t = new Thread(new Worker());
t.start();
// ... 後續需要停止
t.interrupt(); // 設置中斷標誌
t.join(); // 等待實際終止
原則:阻塞方法(sleep/wait/join/Channel IO)拋出 InterruptedException 時,必須 interrupt() 重置標誌或向外拋出,絕不吞噬異常。
Thread high = new Thread(() -> { for(int i=0;i<5;i++) System.out.println("H"); }, "High");
Thread low = new Thread(() -> { for(int i=0;i<5;i++) System.out.println("L"); }, "Low");
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
high.start(); low.start();
// 輸出多為 H...H L...L,但不保證絕對搶佔,受 OS 調度影響
守護線程驗證:
Thread daemon = new Thread(() -> {
while (true) { System.out.println("Daemon running"); sleep(100); }
});
daemon.setDaemon(true);
daemon.start();
System.out.println("Main end"); // 主線程結束,JVM 退出,daemon 立即被殺掉
Executors 或自定義 ThreadPoolExecutor,避免頻繁創建銷毀開銷與 OOM。ThreadLocal、Atomic*、ConcurrentHashMap、BlockingQueue 替代手工 synchronized。synchronized 塊範圍,避免在鎖內調用外部方法/IO。tryLock(timeout)、避免嵌套鎖。new Thread(task, "Order-Process-%d"),便於 jstack/Arthas 排查。submit 配合 Future.get() 捕獲異常,或設置 Thread.UncaughtExceptionHandler。Java 多線程的本質是共享內存並發模型下的協作與競爭。掌握線程生命週期、創建解耦、狀態流轉、中斷協作、鎖機制,並養成「線程池優先、不可變優先、高級組件優先」的工程習慣,才能寫出高併發、低延遲、可維護的並發程序。下一步建議深入學習 java.util.concurrent 包源碼(AQS、CAS、各類 Queue、Executor 框架)與 JVM 內存模型(happens-before、volatile、final 語義)。