最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)常見(jiàn)問(wèn)題之死鎖/活鎖/饑餓的排查與解決方法

 更新時(shí)間:2026年03月05日 10:02:53   作者:C雨后彩虹  
Java死鎖問(wèn)題是多線程編程中一個(gè)經(jīng)典且極具挑戰(zhàn)性的并發(fā)控制問(wèn)題,它直接影響程序的穩(wěn)定性、性能和可維護(hù)性,這篇文章主要介紹了Java并發(fā)常見(jiàn)問(wèn)題之死鎖/活鎖/饑餓的排查與解決方法的相關(guān)資料,需要的朋友可以參考下

一、前言

在多線程并發(fā)編程中,除了數(shù)據(jù)安全問(wèn)題,線程協(xié)作異常是另一類高頻問(wèn)題,其中死鎖、活鎖、饑餓是最典型的三類問(wèn)題。這些問(wèn)題會(huì)導(dǎo)致線程無(wú)法正常執(zhí)行、系統(tǒng)性能下降甚至服務(wù)不可用,且排查難度高 —— 死鎖可能隱藏?cái)?shù)月,在高并發(fā)場(chǎng)景下才會(huì)觸發(fā)。

本文將深入剖析這三類問(wèn)題的產(chǎn)生原因、典型場(chǎng)景、排查方法,并提供可落地的解決方案與避坑指南。

二、死鎖(Deadlock)

1. 死鎖的定義

死鎖是指兩個(gè)或多個(gè)線程互相持有對(duì)方所需的鎖,且都不釋放自己持有的鎖,導(dǎo)致所有線程永久阻塞,無(wú)法繼續(xù)執(zhí)行的狀態(tài)。

2. 死鎖的四大必要條件(缺一不可)

只有同時(shí)滿足以下 4 個(gè)條件,才會(huì)產(chǎn)生死鎖:

  1. 互斥條件:鎖資源只能被一個(gè)線程持有,其他線程無(wú)法獲取;

  2. 持有并等待條件:線程持有已獲取的鎖,同時(shí)等待其他線程持有的鎖;

  3. 不可剝奪條件:線程持有的鎖不能被強(qiáng)制剝奪,只能由線程主動(dòng)釋放;

  4. 循環(huán)等待條件:線程 A 等待線程 B 的鎖,線程 B 等待線程 A 的鎖,形成循環(huán)等待鏈。

3. 死鎖典型案例

/**
 * 死鎖演示:線程1持有鎖A,等待鎖B;線程2持有鎖B,等待鎖A
 */
public class DeadlockDemo {
    // 定義兩個(gè)鎖對(duì)象
    private static final Object LOCK_A = new Object();
    private static final Object LOCK_B = new Object();
    public static void main(String[] args) {
        // 線程1:獲取LOCK_A → 等待LOCK_B
        new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,等待LOCK_B");
                try {
                    Thread.sleep(1000); // 放大死鎖概率
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_B) {
                    System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
                }
            }
        }, "線程1").start();
        // 線程2:獲取LOCK_B → 等待LOCK_A
        new Thread(() -> {
            synchronized (LOCK_B) {
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,等待LOCK_A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_A) {
                    System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,執(zhí)行完成");
                }
            }
        }, "線程2").start();
    }
}

運(yùn)行結(jié)果 :兩個(gè)線程互相等待對(duì)方的鎖,永久阻塞,無(wú)后續(xù)輸出。

4. 死鎖的排查方法

方法 1:jstack 命令(最常用)

1.執(zhí)行 jps 命令,獲取進(jìn)程 ID:

jps
# 輸出示例:1234 DeadlockDemo

2.執(zhí)行 jstack <進(jìn)程ID> ,查看線程狀態(tài):

jstack 1234

3.關(guān)鍵輸出(死鎖提示):

Found one Java-level deadlock:
=============================
"線程2":
  waiting to lock monitor 0x00007f9e3c006800 (object 0x000000076ab60eb0, a java.lang.Object),
  which is held by "線程1"
"線程1":
  waiting to lock monitor 0x00007f9e3c009000 (object 0x000000076ab60ec0, a java.lang.Object),
  which is held by "線程2"

方法 2:JConsole 可視化工具

  1. 啟動(dòng) JConsole(JDK/bin 目錄下),連接目標(biāo)進(jìn)程;

  2. 切換到「線程」標(biāo)簽頁(yè),點(diǎn)擊「檢測(cè)死鎖」,自動(dòng)識(shí)別死鎖線程及鎖信息。

5. 死鎖的解決方案

核心思路: 破壞死鎖的四大必要條件之一 ,常用方案:

方案 1:統(tǒng)一鎖獲取順序(破壞循環(huán)等待條件)

所有線程按相同的順序獲取鎖,避免循環(huán)等待:

// 優(yōu)化后:線程1和線程2都先獲取LOCK_A,再獲取LOCK_B
new Thread(() -> {
    synchronized (LOCK_A) {
        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,等待LOCK_B");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (LOCK_B) {
            System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
        }
    }
}, "線程1").start();
new Thread(() -> {
    synchronized (LOCK_A) { // 統(tǒng)一先獲取LOCK_A
        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,等待LOCK_B");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (LOCK_B) {
            System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
        }
    }
}, "線程2").start();

方案 2:使用可中斷鎖(破壞不可剝奪條件)

使用 ReentrantLock 的 lockInterruptibly() 方法,允許線程被中斷,主動(dòng)釋放鎖:

import java.util.concurrent.locks.ReentrantLock;
public class DeadlockResolveByInterrupt {
    private static final ReentrantLock LOCK_A = new ReentrantLock();
    private static final ReentrantLock LOCK_B = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                // 可中斷的鎖獲取
                LOCK_A.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,等待LOCK_B");
                Thread.sleep(1000);
                LOCK_B.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 被中斷,釋放鎖");
                if (LOCK_A.isHeldByCurrentThread()) {
                    LOCK_A.unlock();
                }
                if (LOCK_B.isHeldByCurrentThread()) {
                    LOCK_B.unlock();
                }
            }
        }, "線程1");
        Thread thread2 = new Thread(() -> {
            try {
                LOCK_B.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,等待LOCK_A");
                Thread.sleep(1000);
                LOCK_A.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,執(zhí)行完成");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 被中斷,釋放鎖");
                if (LOCK_A.isHeldByCurrentThread()) {
                    LOCK_A.unlock();
                }
                if (LOCK_B.isHeldByCurrentThread()) {
                    LOCK_B.unlock();
                }
            }
        }, "線程2");
        thread1.start();
        thread2.start();
        // 等待3秒,若檢測(cè)到死鎖,中斷線程1
        Thread.sleep(3000);
        if (thread1.isAlive() && thread2.isAlive()) {
            thread1.interrupt();
            System.out.println("檢測(cè)到死鎖,中斷線程1");
        }
    }
}

方案 3:使用超時(shí)獲取鎖(破壞持有并等待條件)

使用 ReentrantLock 的 tryLock(long time, TimeUnit unit) 方法,超時(shí)未獲取鎖則放棄,避免永久等待:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockResolveByTimeout {
    private static final ReentrantLock LOCK_A = new ReentrantLock();
    private static final ReentrantLock LOCK_B = new ReentrantLock();
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                if (LOCK_A.tryLock(2, TimeUnit.SECONDS)) { // 超時(shí)2秒
                    System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,等待LOCK_B");
                    Thread.sleep(1000);
                    if (LOCK_B.tryLock(2, TimeUnit.SECONDS)) {
                        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
                        LOCK_B.unlock();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 超時(shí)未獲取LOCK_B,釋放LOCK_A");
                        LOCK_A.unlock();
                    }
                    LOCK_A.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "線程1").start();
        new Thread(() -> {
            try {
                if (LOCK_B.tryLock(2, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,等待LOCK_A");
                    Thread.sleep(1000);
                    if (LOCK_A.tryLock(2, TimeUnit.SECONDS)) {
                        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,執(zhí)行完成");
                        LOCK_A.unlock();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 超時(shí)未獲取LOCK_A,釋放LOCK_B");
                        LOCK_B.unlock();
                    }
                    LOCK_B.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "線程2").start();
    }
}

三、活鎖(Livelock)

1. 活鎖的定義

活鎖是指線程沒(méi)有阻塞,但因互相謙讓(或重試),導(dǎo)致始終無(wú)法獲取所需資源,程序無(wú)法推進(jìn)的狀態(tài)。與死鎖的核心區(qū)別:死鎖是線程完全阻塞,活鎖是線程一直在執(zhí)行,但無(wú)實(shí)際進(jìn)展。

2. 活鎖典型案例

/**
 * 活鎖演示:兩個(gè)線程互相釋放鎖,重試獲取對(duì)方的鎖,始終無(wú)法執(zhí)行完成
 */
public class LivelockDemo {
    private static final ReentrantLock LOCK_A = new ReentrantLock();
    private static final ReentrantLock LOCK_B = new ReentrantLock();
    public static void main(String[] args) {
        // 線程1:獲取LOCK_A失敗 → 釋放LOCK_B(若持有)→ 重試
        new Thread(() -> {
            while (true) {
                try {
                    if (LOCK_A.tryLock(100, TimeUnit.MILLISECONDS)) {
                        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,嘗試獲取LOCK_B");
                        if (LOCK_B.tryLock(100, TimeUnit.MILLISECONDS)) {
                            System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,執(zhí)行完成");
                            LOCK_B.unlock();
                            LOCK_A.unlock();
                            break; // 執(zhí)行完成,退出循環(huán)
                        } else {
                            System.out.println(Thread.currentThread().getName() + " 獲取LOCK_B失敗,釋放LOCK_A");
                            LOCK_A.unlock();
                            Thread.sleep(100); // 謙讓,重試
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "線程1").start();
        // 線程2:獲取LOCK_B失敗 → 釋放LOCK_A(若持有)→ 重試
        new Thread(() -> {
            while (true) {
                try {
                    if (LOCK_B.tryLock(100, TimeUnit.MILLISECONDS)) {
                        System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_B,嘗試獲取LOCK_A");
                        if (LOCK_A.tryLock(100, TimeUnit.MILLISECONDS)) {
                            System.out.println(Thread.currentThread().getName() + " 獲取到LOCK_A,執(zhí)行完成");
                            LOCK_A.unlock();
                            LOCK_B.unlock();
                            break;
                        } else {
                            System.out.println(Thread.currentThread().getName() + " 獲取LOCK_A失敗,釋放LOCK_B");
                            LOCK_B.unlock();
                            Thread.sleep(100); // 謙讓,重試
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "線程2").start();
    }
}

運(yùn)行結(jié)果 :兩個(gè)線程反復(fù)獲取鎖、釋放鎖、重試,始終無(wú)法同時(shí)獲取兩個(gè)鎖,陷入無(wú)限循環(huán)。

3. 活鎖的解決方案

核心思路: 打破 “同步重試” 的循環(huán) ,常用方案:

1.隨機(jī)重試延遲:每個(gè)線程重試時(shí)使用隨機(jī)的休眠時(shí)間,避免同步謙讓;

// 替換固定休眠時(shí)間為隨機(jī)時(shí)間
Thread.sleep(new Random().nextInt(500));

2.優(yōu)先級(jí)機(jī)制:為線程設(shè)置不同的優(yōu)先級(jí),讓部分線程優(yōu)先獲取資源;

3.限制重試次數(shù):設(shè)置最大重試次數(shù),超過(guò)次數(shù)則放棄并報(bào)警,避免無(wú)限循環(huán)。

四、饑餓(Starvation)

1. 饑餓的定義

饑餓是指某些線程因優(yōu)先級(jí)低、或始終競(jìng)爭(zhēng)不到鎖資源,導(dǎo)致長(zhǎng)期無(wú)法執(zhí)行的狀態(tài)。例如:高優(yōu)先級(jí)線程持續(xù)占用 CPU,低優(yōu)先級(jí)線程始終無(wú)法執(zhí)行;非公平鎖下,某些線程始終搶不到鎖。

2. 饑餓典型案例

/**
 * 饑餓演示:高優(yōu)先級(jí)線程持續(xù)占用鎖,低優(yōu)先級(jí)線程長(zhǎng)期無(wú)法獲取鎖
 */
public class StarvationDemo {
    private static final Object LOCK = new Object();
    public static void main(String[] args) {
        // 低優(yōu)先級(jí)線程
        Thread lowPriorityThread = new Thread(() -> {
            int count = 0;
            while (true) {
                synchronized (LOCK) {
                    System.out.println(Thread.currentThread().getName() + " 執(zhí)行第" + (++count) + "次");
                    try {
                        Thread.sleep(100); // 持有鎖時(shí)間短,但仍被高優(yōu)先級(jí)線程搶占
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "低優(yōu)先級(jí)線程");
        lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 優(yōu)先級(jí)1
        // 高優(yōu)先級(jí)線程
        Thread highPriorityThread = new Thread(() -> {
            int count = 0;
            while (true) {
                synchronized (LOCK) {
                    System.out.println(Thread.currentThread().getName() + " 執(zhí)行第" + (++count) + "次");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "高優(yōu)先級(jí)線程");
        highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 優(yōu)先級(jí)10
        lowPriorityThread.start();
        highPriorityThread.start();
    }
}

運(yùn)行結(jié)果 :高優(yōu)先級(jí)線程的執(zhí)行次數(shù)遠(yuǎn)多于低優(yōu)先級(jí)線程,低優(yōu)先級(jí)線程長(zhǎng)期 “饑餓”。

3. 饑餓的解決方案

核心思路: 保證資源分配的公平性 ,常用方案:

1.使用公平鎖: ReentrantLock 的公平鎖模式保證線程按 FIFO 順序獲取鎖,避免插隊(duì);

private static final ReentrantLock LOCK = new ReentrantLock(true); // 公平鎖

2.避免線程優(yōu)先級(jí)差異:盡量將線程優(yōu)先級(jí)設(shè)置為相同(默認(rèn) 5),減少調(diào)度器的偏好;

3.減少鎖持有時(shí)間:縮短同步代碼塊的執(zhí)行時(shí)間,讓鎖盡快釋放,增加低優(yōu)先級(jí)線程的獲取機(jī)會(huì);

4.使用線程池:線程池的工作線程優(yōu)先級(jí)一致,且有任務(wù)隊(duì)列緩沖,避免個(gè)別線程長(zhǎng)期搶占資源。

五、并發(fā)問(wèn)題對(duì)比

問(wèn)題類型

核心特征

線程狀態(tài)

排查難度

核心解決方案

死鎖

互相持有鎖,永久阻塞

BLOCKED

中(jstack 可直接檢測(cè))

統(tǒng)一鎖順序、超時(shí)獲取、可中斷鎖

活鎖

無(wú)阻塞,但互相謙讓,無(wú)進(jìn)展

RUNNABLE

高(無(wú)明顯報(bào)錯(cuò),需分析日志)

隨機(jī)重試延遲、優(yōu)先級(jí)機(jī)制、限制重試次數(shù)

饑餓

長(zhǎng)期競(jìng)爭(zhēng)不到資源,偶爾執(zhí)行

RUNNABLE

中(需統(tǒng)計(jì)執(zhí)行頻率)

公平鎖、統(tǒng)一優(yōu)先級(jí)、減少鎖持有時(shí)間

六、實(shí)戰(zhàn)避坑指南

1. 預(yù)防死鎖的最佳實(shí)踐

  • 最小化鎖范圍:僅在必要的代碼塊加鎖,縮短鎖持有時(shí)間;

  • 避免嵌套鎖:盡量不使用多層鎖嵌套,若必須使用,嚴(yán)格統(tǒng)一鎖獲取順序;

  • 使用定時(shí)鎖:優(yōu)先使用 tryLock(timeout) 替代無(wú)超時(shí)的鎖獲取;

  • 監(jiān)控鎖狀態(tài):通過(guò) JMX/APM 工具監(jiān)控鎖的持有時(shí)間、競(jìng)爭(zhēng)次數(shù),提前發(fā)現(xiàn)死鎖風(fēng)險(xiǎn)。

2. 通用優(yōu)化建議

  • 優(yōu)先使用并發(fā)工具: ConcurrentHashMap 、 CountDownLatch 等工具已封裝安全的并發(fā)邏輯,避免手動(dòng)加鎖;

  • 避免手動(dòng)線程管理:使用線程池( ThreadPoolExecutor )替代手動(dòng)創(chuàng)建線程,統(tǒng)一管理線程生命周期;

  • 增加容錯(cuò)機(jī)制:關(guān)鍵業(yè)務(wù)線程設(shè)置超時(shí)、重試、降級(jí)邏輯,避免因并發(fā)問(wèn)題導(dǎo)致服務(wù)不可用;

  • 壓測(cè)驗(yàn)證:上線前通過(guò)高并發(fā)壓測(cè),模擬極端場(chǎng)景,提前暴露死鎖 / 活鎖 / 饑餓問(wèn)題。

七、總結(jié)

本文深入剖析了 Java 并發(fā)編程中死鎖、活鎖、饑餓三類典型問(wèn)題的產(chǎn)生原因、典型場(chǎng)景與解決方案。死鎖是最致命的問(wèn)題,需通過(guò)破壞四大必要條件來(lái)預(yù)防;活鎖需打破同步重試的循環(huán);饑餓需保證資源分配的公平性。在實(shí)際開(kāi)發(fā)中,應(yīng)遵循 “預(yù)防大于排查” 的原則:通過(guò)統(tǒng)一鎖順序、使用公平鎖、縮短鎖持有時(shí)間等手段,從根源減少并發(fā)問(wèn)題的發(fā)生;同時(shí)掌握 jstack、JConsole 等排查工具,快速定位已出現(xiàn)的問(wèn)題。

到此這篇關(guān)于Java并發(fā)常見(jiàn)問(wèn)題之死鎖/活鎖/饑餓的排查與解決方法的文章就介紹到這了,更多相關(guān)Java并發(fā)死鎖/活鎖/饑餓內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

沂源县| 弥勒县| 金乡县| 康保县| 保靖县| 阆中市| 醴陵市| 云和县| 浦江县| 德兴市| 资中县| 桦川县| 五台县| 乳山市| 河池市| 磴口县| 荆门市| 宝丰县| 修武县| 天祝| 宝丰县| 平塘县| 盘山县| 涪陵区| 昌都县| 临西县| 西丰县| 弥勒县| 茌平县| 铜川市| 仁布县| 青州市| 如皋市| 航空| 汪清县| 金昌市| 林口县| 壶关县| 宾阳县| 朔州市| 兴仁县|