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

Java 多線程進階:鎖機制、生產消費模式與線程池實戰

     編輯:Java 綜合教學

JDK 5+ 顯式鎖 Lock 與 ReentrantLock 實戰

java.util.concurrent.locks.Lock 接口提供比 synchronized 更靈活的同步控制:支持可中斷獲取鎖(lockInterruptibly())、超時獲取(tryLock(timeout))、公平/非公平策略、條件變量(newCondition())。ReentrantLock 是其標準實現,內部基於 AQS(AbstractQueuedSynchronizer)隊列同步器構建。

private final Lock lock = new ReentrantLock(); // 非公平鎖
// private final Lock lock = new ReentrantLock(true); // 公平鎖

public void run() {
    while (true) {
        lock.lock(); // 必須在 try 外獲取,確保 finally 能釋放
        try {
            if (tickets > 0) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + " 出售第 " + tickets-- + " 張票");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 保證釋放
        }
    }
}

死鎖成因與排查

死鎖四必要條件:互斥、請求與保持、不剝佔、循環等待。典型場景為多線程以不同順序獲取多把鎖。

// 死鎖復現:線程 1 鎖 A→B,線程 2 鎖 B→A
static final Object lockA = new Object(), lockB = new Object();

new Thread(() -> {
    synchronized (lockA) {
        sleep(100);
        synchronized (lockB) { /* ... */ }
    }
}).start();

new Thread(() -> {
    synchronized (lockB) {
        sleep(100);
        synchronized (lockA) { /* ... */ }
    }
}).start();

排查手段:jstack PID 生成線程快照,搜尋 "Found one Java-level deadlock";或使用 JConsole/VisualVM 圖形化檢測。預防原則:統一鎖獲取順序、使用 tryLock(timeout) 避免無限等待、縮小同步塊範圍。

生產者-消費者模式演進

版本 1:共享對象傳遞

通過構造器注入同一 Student 實例,解決「各線程操作不同對象」導致的數據不一致。

版本 2:同步塊保護複合操作

「設置姓名+年齡」、「讀取姓名+年齡」屬於複合操作,需同步。關鍵點:生產/消費線程必須使用同一把鎖(此處為共享對象 s)。

版本 3:wait/notify 實現「生產一個、消費一個」

引入 boolean flag 標記缓衝區狀態:

// 生產者
synchronized (s) {
    while (s.flag) s.wait(); // 滿倉等待
    s.setData(...);
    s.flag = true;
    s.notify(); // 喚醒消費者
}

// 消費者
synchronized (s) {
    while (!s.flag) s.wait(); // 空倉等待
    s.getData();
    s.flag = false;
    s.notify(); // 喚醒生產者
}

核心規則:wait/notify/notifyAll 必須在同步塊內調用,且由鎖對象調用;wait() 釋放鎖並進入 WAITING 狀態,被喚醒後重新競爭鎖,從 wait() 之後繼續執行。

版本 4:封裝同步邏輯至資源類(最終版)

將 set/get 方法聲明為 synchronized,內部自管 wait/notify,調用者無需關心同步細節,體現「高內聚、低耦合」。

public class Student {
    private String name; private int age; private boolean flag = false;
    public synchronized void set(String name, int age) {
        while (flag) { try { wait(); } catch (InterruptedException e) {} }
        this.name = name; this.age = age; flag = true; notify();
    }
    public synchronized void get() {
        while (!flag) { try { wait(); } catch (InterruptedException e) {} }
        System.out.println(name + "---" + age); flag = false; notify();
    }
}

線程生命週期與狀態轉換

線程狀態轉換圖

  • NEW → RUNNABLE(start())
  • RUNNABLE ↔ BLOCKED(鎖競爭)/ WAITING(wait()/join())/ TIMED_WAITING(sleep()/wait(timeout))
  • RUNNABLE → TERMINATED(run() 結束或異常拋出)

線程組 ThreadGroup 批量管理

ThreadGroup group = new ThreadGroup("BatchGroup");
group.setDaemon(true); // 整組設置為守護線程
new Thread(group, task, "Worker-1").start();
new Thread(group, task, "Worker-2").start();
// 批量中斷
group.interrupt();
// 遞歸枚舉
Thread[] list = new Thread[group.activeCount()];
group.enumerate(list);

線程池 Executor 框架詳解

工廠方法特性適用場景
newFixedThreadPool(n)固定核心數,無界隊列長期任務、控制並發度
newCachedThreadPool()核心 0,最大 Integer.MAX_VALUE,60s 空閒回收大量短期異步任務
newSingleThreadExecutor()單線程串行,保證順序順序執行、UI 事件分發
newScheduledThreadPool(n)支持定時/周期任務定時器替代品
ExecutorService pool = Executors.newFixedThreadPool(2);
Future f1 = pool.submit(() -> { int sum=0; for(int i=1;i<=100;i++) sum+=i; return sum; });
Future f2 = pool.submit(new Callable() { ... }); // JDK 7 匿名內部類寫法
System.out.println(f1.get() + ", " + f2.get()); // 阻塞獲取結果
pool.shutdown(); // 拒絕新任務,等待現有任務完成
// pool.shutdownNow(); // 嘗試中斷執行中任務

Callable 與 Future 模式

Callable 支持返回值與拋出受檢異常,配合 Future 實現異步計算結果獲取。Future.get() 會阻塞直到任務完成;可用 future.cancel(true) 嘗試取消。

定時器 Timer 與 TimerTask

Timer timer = new Timer();
// 延遲 3s 執行一次
timer.schedule(new TimerTask() {
    public void run() { System.out.println("Boom!"); timer.cancel(); }
}, 3000);

// 3s 後首次,之後每 2s 執行
timer.schedule(new TimerTask() {
    public void run() { System.out.println("Periodic"); }
}, 3000, 2000);

// 指定時間點觸發
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = sdf.parse("2025-12-31 23:59:59");
timer.schedule(task, time);

注意:Timer 單線程串行,任務耗時長會延遲後續任務;生產環境建議使用 ScheduledThreadPoolExecutor 或 Quartz/Spring Task。

定時清理目錄實戰

class DeleteFolderTask extends TimerTask {
    public void run() {
        deleteRecursively(new File("demo"));
    }
    void deleteRecursively(File dir) {
        File[] files = dir.listFiles();
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory()) deleteRecursively(f);
                else f.delete();
            }
        }
        dir.delete();
    }
}

面試高頻考點總結

  1. 線程創建方式:繼承 Thread、實現 Runnable、實現 Callable+線程池(3 種)
  2. 同步方式:同步代碼塊、同步方法、Lock 顯式鎖、volatile、ThreadLocal、原子類、並發集合
  3. start() vs run():start() 啟動新線程由 JVM 調用 run();直接調用 run() 為主線程同步執行
  4. sleep vs wait:sleep 不釋放鎖、指定時間;wait 釋放鎖、可無參無限等待、需同步塊內調用
  5. wait/notify 定義在 Object:因鎖對象任意,監視器綁定於對象頭 Mark Word,故屬於 Object 基礎協議
  6. 線程池拒絕策略:AbortPolicy(默認拋異常)、CallerRunsPolicy(調用者執行)、DiscardPolicy(丟棄)、DiscardOldestPolicy(丟棄最舊)

面向對象設計六大原則(SOLID+)

  • 單一職責 SRP:類只有一個變化原因,高內聚低耦合
  • 開閉原則 OCP:對擴展開放、對修改關閉;抽象化是關鍵
  • 裡氏替換 LSP:子類必須能替換父類,不破壞父類契約
  • 依賴倒置 DIP:依賴抽象不依賴具體;框架設計核心
  • 接口隔離 ISP:接口細粒度、單一角色;拒絕「胖接口」
  • 迪米特法則 LoD:最少知識原則,降低耦合

23 種設計模式分類速覽

線程組示例

  • 創建型(6):簡單工廠、工廠方法、抽象工廠、建造者、原型、單例
  • 結構型(7):外觀、適配器、代理、裝飾、橋接、組合、享元
  • 行為型(10):模板方法、觀察者、狀態、責任鏈、命令、訪問者、策略、備忘錄、迭代器、解釋器

單例模式:餓漢 vs 懶漢

// 餓漢式:類加載即創建,線程安全,無延遲加載
public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() { return INSTANCE; }
}

// 懶漢式(雙重檢查鎖 DCL):延遲加載 + 線程安全
public class LazySingleton {
    private static volatile LazySingleton instance; // volatile 禁止指令重排
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) instance = new LazySingleton();
            }
        }
        return instance;
    }
}

JDK Runtime.getRuntime() 採用餓漢式單例,提供 exec() 執行系統命令(如 shutdown -s -t 10000 定時關機,shutdown -a 取消)。

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