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

Java 多線程核心精講:從線程創建到並發控制的完整實戰

     編輯:Java 程式設計入門

前言

多線程是 Java 後端開發的核心竞争力,也是面試必考重災區。本文系統梳理線程生命週期、創建方式、屬性控制、狀態流轉、同步機制、線程間通信、優雅停止等核心知識點,並通過可運行代碼示例驗證關鍵行為,幫助你建立扎實的並發編程思維模型。

一、線程基礎認知

1. JVM 視角的線程模型

  • 操作系統調度單位是線程,進程是資源容器。
  • 即使寫單線程 main 方法,JVM 內部至少運行:主線程、GC 線程、JIT 編譯線程、Reference Handler、Finalizer、Signal Dispatcher 等。
  • Java 線程 1:1 映射到內核線程,調度完全交給 OS。

2. 線程核心屬性

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()

代碼示例:Runnable 實現「單對象多線程」

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) 保護。

三、線程狀態機與流轉

Thread State Diagram(此處建議補充標準 6 狀態圖)

  • NEW:new Thread(),未調用 start()。
  • RUNNABLE:start() 後,就緒/運行合併,等待 CPU 時間片。
  • BLOCKED:等待 synchronized 鎖(非 java.util.concurrent 鎖)。
  • WAITING:Object.wait()、Thread.join()、LockSupport.park(),無超時。
  • TIMED_WAITING:Thread.sleep()、wait(timeout)、join(timeout)、parkNanos。
  • TERMINATED:run() 正常返回或拋出未捕獲異常。

關鍵點:sleep() 不釋放鎖;wait() 釋放鎖並進入 WAITING;yield() 僅讓出 CPU,狀態仍 RUNNABLE。

四、線程協作核心 API 實戰

1. 等待/通知機制(Object Monitor)

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 防止虛假喚醒。

2. Thread.join():等待線程終止

Thread t = new Thread(() -> { /* 耗時任務 */ });
t.start();
// 主線程做其他事...
t.join(); // 阻塞直到 t 終止
System.out.println("子線程已結束");

重載 join(millis) 支援超時,避免無限等待。

3. 線程間通信:PipedInputStream/OutputStream

適用於生產者-消費者字節流場景,內部基於環形緩衝區(默認 1KB),非高性能方案,高吞吐請用 Disruptor 或 BlockingQueue。

五、優雅停止線程:拒絕 stop()/suspend()

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 立即被殺掉

七、實戰最佳實踐清單

  1. 優先使用線程池:Executors 或自定義 ThreadPoolExecutor,避免頻繁創建銷毀開銷與 OOM。
  2. 任務提交用 Callable + Future:需返回值/異常處理場景。
  3. 共享可變狀態最小化:優先用 ThreadLocal、Atomic*、ConcurrentHashMap、BlockingQueue 替代手工 synchronized。
  4. 鎖粒度最小化:縮小 synchronized 塊範圍,避免在鎖內調用外部方法/IO。
  5. 死鎖預防:固定加鎖順序、使用 tryLock(timeout)、避免嵌套鎖。
  6. 線程命名規範:new Thread(task, "Order-Process-%d"),便於 jstack/Arthas 排查。
  7. 異常處理:線程池提交任務用 submit 配合 Future.get() 捕獲異常,或設置 Thread.UncaughtExceptionHandler。

八、總結

Java 多線程的本質是共享內存並發模型下的協作與競爭。掌握線程生命週期、創建解耦、狀態流轉、中斷協作、鎖機制,並養成「線程池優先、不可變優先、高級組件優先」的工程習慣,才能寫出高併發、低延遲、可維護的並發程序。下一步建議深入學習 java.util.concurrent 包源碼(AQS、CAS、各類 Queue、Executor 框架)與 JVM 內存模型(happens-before、volatile、final 語義)。

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