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

Java 執行緒優先級與死鎖:原理、示範與避免策略

     編輯:Java 相關知識

執行緒優先級與死鎖是併發編程中兩個既基礎又極易踩坑的主題。本文深入剖析優先級調度的真實行為、死鎖形成的四個必要條件、經典死鎖代碼示範,並提供工程級避免與排查策略。

一、執行緒優先級:提示而非保證

1. API 基礎


Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5 (預設)
Thread.MAX_PRIORITY = 10

thread.setPriority(Thread.MAX_PRIORITY);
thread.getPriority();

2. 調度真相

  • 搶佔式:高優先級可搶佔低優先級(理論上)。
  • 時間片輪轉:同優先級執行緒分時片(Windows);或需主動讓出(Solaris/Linux CFS)。
  • 平台差異巨大:Windows 10 級映射到 OS 7 級;Linux 忽略 Java 優先級,完全由 CFS 公平調度。
  • 不要依賴優先級保證業務順序:它只是調度器的「建議」,非硬性約束。

3. 何時適合調整

  • 後台維護任務(GC、日誌清理)設 MIN_PRIORITY。
  • 即時響應任務(UI 事件、心跳檢測)設 MAX_PRIORITY。
  • 核心業務邏輯保持 NORM_PRIORITY,避免優先級反轉。

二、死鎖:四個必要條件(Coffman Conditions)

  1. 互斥:資源一次只能被一執行緒持有(鎖)。
  2. 持有並等待:執行緒持有資源 A,同時等待資源 B。
  3. 不可搶佔:持有的資源只能自願釋放,不可被強制奪走。
  4. 循環等待:T1 等 T2 持有的資源,T2 等 T3... Tn 等 T1。

**破壞任一條件即可避免死鎖**。

三、經典死鎖代碼示範


public class DeadlockDemo {
    private static final Object LOCK_A = new Object();
    private static final Object LOCK_B = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK_A) {
                log("T1 持有 A,等待 B...");
                sleep(1000); // 增加死鎖機率
                synchronized (LOCK_B) { log("T1 獲得 B"); }
            }
        }, "T1");

        Thread t2 = new Thread(() -> {
            synchronized (LOCK_B) {
                log("T2 持有 B,等待 A...");
                sleep(1000);
                synchronized (LOCK_A) { log("T2 獲得 A"); }
            }
        }, "T2");

        t1.start(); t2.start();
    }
    static void log(String msg) { System.out.println(Thread.currentThread().getName() + ": " + msg); }
    static void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
}

輸出(程式卡住,需 Ctrl+Break 產生 Thread Dump)


T1: T1 持有 A,等待 B...
T2: T2 持有 B,等待 A...

Thread Dump 關鍵證據


"T1" waiting for <0x000000076b2c1b68> (a java.lang.Object)   // LOCK_B
    - waiting to lock <0x000000076b2c1b68>
    - locked <0x000000076b2c1b58> (a java.lang.Object)      // LOCK_A

"T2" waiting for <0x000000076b2c1b58> (a java.lang.Object)   // LOCK_A
    - waiting to lock <0x000000076b2c1b58>
    - locked <0x000000076b2c1b68> (a java.lang.Object)      // LOCK_B

四、工程級避免策略

1. 固定鎖順序(破壞循環等待)


// 所有執行緒按相同順序獲取鎖:hashCode 比較或業務 ID 排序
public void transfer(Account from, Account to, int amt) {
    Object first = from.hashCode() < to.hashCode() ? from : to;
    Object second = first == from ? to : from;
    synchronized (first) {
        synchronized (second) { /* 轉帳邏輯 */ }
    }
}

2. 使用 tryLock 超時機制(破壞不可搶佔/持有並等待)


Lock lockA = new ReentrantLock();
Lock lockB = new ReentrantLock();

boolean gotA = lockA.tryLock(1, TimeUnit.SECONDS);
if (gotA) {
    try {
        boolean gotB = lockB.tryLock(1, TimeUnit.SECONDS);
        if (gotB) { try { /* 安全區 */ } finally { lockB.unlock(); } }
        else { log.warn("獲取 B 超時,釋放 A"); }
    } finally { lockA.unlock(); }
}

3. 縮小鎖範圍 / 無鎖資料結構

  • 用 ConcurrentHashMap, AtomicLong, LongAdder 替代 synchronized。
  • 同步區塊內**嚴禁**呼叫外部方法、I/O、sleep。

4. 避免巢狀同步塊

若必須多鎖,統一用 Lock 並按順序 lock(),或用 Lock.tryLock() 全獲取或全放棄。

五、死鎖排查工具鏈

  1. jstack / jcmd Thread.print:產生 Thread Dump,搜尋 BLOCKED、waiting for、locked。
  2. VisualVM / JConsole / Arthas:圖形化分析鎖依賴圖。
  3. -XX:+PrintConcurrentLocks:JVM 參數輸出 ReentrantLock 持有者資訊。
  4. 應用層:ThreadMXBean.findDeadlockedThreads() 程式化偵測,可整合至健康檢查端點。

六、活鎖與飢餓:近親區別

現象描述典型原因
活鎖執行緒非阻塞但無法推進(互相禮讓)過度禮讓邏輯、CAS 重試風暴
飢餓低優先級/公平鎖下執行緒長期得不到 CPU/鎖高優先級獨佔、非公平鎖持續搶佔

結語

優先級只是調度提示,死鎖源於無序鎖獲取。**固定鎖順序、tryLock 超時、縮小同步範圍、優先用並發容器**是避免死鎖的四大鐵律。上線前必做壓測 + Thread Dump 分析,將死鎖風險消滅在萌芽狀態。

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