欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java死鎖原因及預(yù)防方法超詳細(xì)講解

 更新時(shí)間:2025年06月26日 08:28:51   作者:走過(guò)冬季  
Java死鎖是多線程因相互等待資源而阻塞的問(wèn)題,需滿(mǎn)足互斥、持有并等待、不可剝奪、循環(huán)等待四個(gè)條件,這篇文章主要介紹了Java死鎖原因及預(yù)防方法的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

Java 死鎖是多線程編程中一種經(jīng)典且棘手的問(wèn)題,它會(huì)導(dǎo)致多個(gè)線程相互等待對(duì)方持有的資源而永久阻塞。理解其產(chǎn)生原因和預(yù)防措施至關(guān)重要。

一、 Java 死鎖是如何產(chǎn)生的?

死鎖的發(fā)生需要同時(shí)滿(mǎn)足以下四個(gè)必要條件(缺一不可):

  1. 互斥使用 (Mutual Exclusion):

    • 資源(如對(duì)象鎖、數(shù)據(jù)庫(kù)連接、文件句柄等)一次只能被一個(gè)線程獨(dú)占使用。
    • synchronized 關(guān)鍵字或 Lock 對(duì)象實(shí)現(xiàn)的鎖機(jī)制本質(zhì)上就提供了這種互斥性。
  2. 持有并等待 (Hold and Wait / Partial Allocation):

    • 一個(gè)線程在持有至少一個(gè)資源(鎖)的同時(shí),又去申請(qǐng)獲取另一個(gè)線程當(dāng)前正持有的資源(鎖)。
  3. 不可剝奪 (No Preemption):

    • 一個(gè)線程已經(jīng)獲得的資源(鎖)在它主動(dòng)釋放之前,不能被其他線程強(qiáng)行剝奪。
    • 在 Java 中,synchronized 鎖不能被強(qiáng)制中斷釋放;Lock.lock() 獲取的鎖也不能被其他線程強(qiáng)制解鎖(除非使用 Lock.lockInterruptibly() 并中斷線程,但這通常也不是“強(qiáng)行剝奪”的含義)。
  4. 循環(huán)等待 (Circular Wait):

    • 存在一組等待的線程 {T1, T2, ..., Tn},其中:
      • T1 等待 T2 持有的資源,
      • T2 等待 T3 持有的資源,
      • …,
      • Tn 等待 T1 持有的資源。
    • 所有線程形成一個(gè)等待資源的環(huán)。

經(jīng)典死鎖場(chǎng)景示例(哲學(xué)家就餐問(wèn)題簡(jiǎn)化版)

public class DeadlockExample {

    static final Object lockA = new Object();
    static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lockA) { // 線程1獲取lockA
                System.out.println("Thread1 acquired lockA");
                try {
                    Thread.sleep(100); // 模擬操作,增加死鎖發(fā)生概率
                } catch (InterruptedException e) {}
                synchronized (lockB) { // 線程1嘗試獲取lockB(此時(shí)可能被線程2持有)
                    System.out.println("Thread1 acquired lockB");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lockB) { // 線程2獲取lockB
                System.out.println("Thread2 acquired lockB");
                try {
                    Thread.sleep(100); // 模擬操作,增加死鎖發(fā)生概率
                } catch (InterruptedException e) {}
                synchronized (lockA) { // 線程2嘗試獲取lockA(此時(shí)被線程1持有)
                    System.out.println("Thread2 acquired lockA");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

分析死鎖條件滿(mǎn)足情況

  1. 互斥: lockAlockB 都是 synchronized 使用的對(duì)象,具有互斥性。
  2. 持有并等待:
    • 線程1 持有 lockA,同時(shí)等待獲取 lockB
    • 線程2 持有 lockB,同時(shí)等待獲取 lockA
  3. 不可剝奪: Java synchronized 鎖不能被其他線程強(qiáng)行剝奪。
  4. 循環(huán)等待:
    • 線程1 在等待線程2 釋放的 lockB。
    • 線程2 在等待線程1 釋放的 lockA。
    • 形成了一個(gè)閉環(huán):線程1 -> 等待lockB(被線程2持有) -> 線程2 -> 等待lockA(被線程1持有) -> 線程1。

二、 如何防止 Java 死鎖?

防止死鎖的核心策略就是破壞上述四個(gè)必要條件中的至少一個(gè)。以下是常用的方法:

1. 破壞"循環(huán)等待"條件 - 鎖順序化 (Lock Ordering)

  • 原理: 強(qiáng)制所有線程以全局一致的固定順序獲取鎖。
  • 實(shí)現(xiàn):
    • 為所有需要獲取的鎖定義一個(gè)全局的獲取順序(例如,按對(duì)象的 hashCode、按一個(gè)預(yù)定義的唯一ID、按名稱(chēng)排序等)。
    • 在任何需要獲取多個(gè)鎖的地方,都嚴(yán)格按照這個(gè)全局順序去申請(qǐng)鎖。
  • 效果: 從根本上消除了循環(huán)等待的可能性。如果一個(gè)線程需要鎖 L1 和 L2,并且順序規(guī)定必須先 L1 后 L2,那么所有線程都會(huì)按這個(gè)順序申請(qǐng)。這樣就不會(huì)出現(xiàn)線程1 持 L1 等 L2,而線程2 持 L2 等 L1 的循環(huán)情況。
  • 示例修改: 修改上面的例子,強(qiáng)制兩個(gè)線程都先獲取 lockA,再獲取 lockB
Thread thread1 = new Thread(() -> {
    synchronized (lockA) {
        System.out.println("Thread1 acquired lockA");
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        synchronized (lockB) { // 總是先A后B
            System.out.println("Thread1 acquired lockB");
        }
    }
});

Thread thread2 = new Thread(() -> {
    synchronized (lockA) { // 線程2也先嘗試獲取lockA
        System.out.println("Thread2 acquired lockA");
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        synchronized (lockB) { // 再獲取lockB
            System.out.println("Thread2 acquired lockB");
        }
    }
});
  • 注意: 嚴(yán)格遵守順序是關(guān)鍵。有時(shí)確定一個(gè)一致的全局順序可能比較復(fù)雜(尤其是鎖是動(dòng)態(tài)創(chuàng)建或數(shù)量不確定時(shí)),但這是最推薦、最有效的預(yù)防策略之一??梢允褂?System.identityHashCode(Object) 作為最后手段來(lái)排序,但要注意哈希沖突。

2. 破壞"持有并等待"條件 - 一次性申請(qǐng)所有鎖 (Atomically Acquire All Locks)

  • 原理: 一個(gè)線程在開(kāi)始執(zhí)行任務(wù)前,一次性申請(qǐng)它所需的所有鎖。如果無(wú)法一次性獲取全部鎖,它就不持有任何已獲得的鎖(全部釋放),等待一段時(shí)間再重試或采用其他策略。
  • 實(shí)現(xiàn):
    • 設(shè)計(jì)一個(gè)獲取多個(gè)鎖的機(jī)制(例如,一個(gè)包含所有需要鎖的集合)。
    • 嘗試一次性獲取集合中所有的鎖(通常使用 tryLock)。
    • 如果成功獲取所有鎖,執(zhí)行任務(wù)。
    • 如果獲取任何一個(gè)鎖失?。ǔ瑫r(shí)或立即失敗),則釋放它已經(jīng)成功獲取的所有鎖,然后進(jìn)行回退(等待、重試、放棄任務(wù)等)。
  • 效果: 線程要么同時(shí)持有所有需要的鎖(不等待),要么不持有任何鎖(不保持部分鎖去等待其他鎖),破壞了“持有并等待”。
  • 工具: Java 的 Lock 接口(特別是 ReentrantLock)提供了 tryLock() 方法(可帶超時(shí))來(lái)實(shí)現(xiàn)這種細(xì)粒度控制,這比 synchronized 更靈活。
  • 示例修改 (使用 ReentrantLock 和 tryLock):
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockPrevention {

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

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> acquireLocksAndWork(lockA, lockB, "Thread1"));
        Thread thread2 = new Thread(() -> acquireLocksAndWork(lockB, lockA, "Thread2")); // 注意順序不同,但方法內(nèi)部處理
        thread1.start();
        thread2.start();
    }

    public static void acquireLocksAndWork(Lock firstLock, Lock secondLock, String threadName) {
        while (true) {
            boolean gotFirst = false;
            boolean gotSecond = false;
            try {
                // 嘗試獲取第一個(gè)鎖(帶超時(shí)避免無(wú)限等待)
                gotFirst = firstLock.tryLock(100, TimeUnit.MILLISECONDS);
                if (gotFirst) {
                    System.out.println(threadName + " acquired first lock");
                    // 嘗試獲取第二個(gè)鎖(帶超時(shí))
                    gotSecond = secondLock.tryLock(100, TimeUnit.MILLISECONDS);
                    if (gotSecond) {
                        System.out.println(threadName + " acquired second lock");
                        // 成功獲取兩個(gè)鎖,執(zhí)行工作
                        System.out.println(threadName + " doing work...");
                        Thread.sleep(500); // 模擬工作
                        break; // 工作完成,跳出循環(huán)
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 無(wú)論如何,在退出前確保釋放已獲得的鎖
                if (gotSecond) secondLock.unlock();
                if (gotFirst) firstLock.unlock();
            }
            // 如果沒(méi)能一次性獲得兩個(gè)鎖,等待隨機(jī)時(shí)間后重試,避免活鎖
            try {
                Thread.sleep((long) (Math.random() * 100));
            } catch (InterruptedException e) {}
        }
    }
}

3. 避免不必要的鎖 / 縮小鎖的范圍

  • 原理: 減少鎖的持有時(shí)間和鎖的數(shù)量,從而降低線程在持有鎖期間去請(qǐng)求另一個(gè)鎖的機(jī)會(huì)(破壞持有并等待的機(jī)會(huì)),也減少了形成循環(huán)等待的可能性。
  • 實(shí)現(xiàn):
    • 只鎖必要的代碼塊: 盡可能縮小 synchronized 塊的范圍,只保護(hù)真正需要互斥訪問(wèn)的共享數(shù)據(jù)操作。不要在鎖內(nèi)執(zhí)行耗時(shí)操作(如IO)。
    • 使用線程安全類(lèi): 優(yōu)先使用 ConcurrentHashMap, CopyOnWriteArrayList, AtomicInteger 等并發(fā)容器和原子類(lèi),它們內(nèi)部實(shí)現(xiàn)了高效的并發(fā)控制,減少了你顯式加鎖的需要。
    • 不可變對(duì)象: 使用不可變對(duì)象(final 字段,構(gòu)造后狀態(tài)不變)。訪問(wèn)不可變對(duì)象不需要同步。
    • 線程本地存儲(chǔ): 使用 ThreadLocal 為每個(gè)線程創(chuàng)建變量的副本,避免共享。
  • 效果: 雖然不是直接破壞必要條件,但這是良好的并發(fā)編程實(shí)踐,能顯著降低死鎖發(fā)生的概率和影響范圍。

4. 使用鎖超時(shí) (Lock Timeout) - 破壞"不可剝奪"的間接效果

  • 原理: 在嘗試獲取鎖時(shí),不無(wú)限期等待,而是設(shè)置一個(gè)超時(shí)時(shí)間。如果超時(shí)還沒(méi)獲取到,則放棄當(dāng)前持有的所有鎖(如果需要),釋放資源,進(jìn)行回退(重試、記錄日志、失敗等)。
  • 實(shí)現(xiàn): 主要依賴(lài) Lock 接口的 tryLock(long time, TimeUnit unit) 方法。synchronized 無(wú)法直接實(shí)現(xiàn)超時(shí)。
  • 效果: 它本身并不直接強(qiáng)行剝奪一個(gè)線程已持有的鎖(不破壞“不可剝奪”的本意),但它允許一個(gè)線程主動(dòng)放棄等待(等待超時(shí)),從而打破了死鎖環(huán)中等待的僵局。它破壞了死鎖發(fā)生的“永久阻塞”特性,給了系統(tǒng)恢復(fù)的機(jī)會(huì)。結(jié)合第2點(diǎn)(釋放已持有鎖),效果更好。
  • 示例: 見(jiàn)上面第2點(diǎn)(一次性申請(qǐng)所有鎖)的代碼示例,其中就使用了 tryLock 帶超時(shí)。

5. 死鎖檢測(cè)與恢復(fù)

  • 原理: 不主動(dòng)預(yù)防死鎖,而是允許死鎖發(fā)生,但系統(tǒng)定期檢測(cè)死鎖的存在(如通過(guò)構(gòu)建資源分配圖并檢測(cè)環(huán)),一旦檢測(cè)到,采取強(qiáng)制措施打破死鎖(例如:終止一個(gè)或多個(gè)死鎖線程、剝奪其資源(在Java中很難安全實(shí)現(xiàn)))。
  • Java 實(shí)現(xiàn):
    • 檢測(cè): Java 沒(méi)有內(nèi)置的通用死鎖檢測(cè)API。但可以通過(guò) ThreadMXBeanfindDeadlockedThreads()findMonitorDeadlockedThreads() 方法來(lái)檢測(cè)由 synchronizedownable synchronizers (如 ReentrantLock) 引起的死鎖。JMX 工具(如 JConsole, VisualVM)通常集成了這個(gè)功能。
    • 恢復(fù): Java 本身沒(méi)有提供安全的、標(biāo)準(zhǔn)的線程終止或資源剝奪機(jī)制來(lái)恢復(fù)死鎖。通常檢測(cè)到死鎖后,只能記錄日志、告警,然后人工介入重啟應(yīng)用或相關(guān)服務(wù)。強(qiáng)行終止線程 (Thread.stop()) 是極其危險(xiǎn)已被廢棄的方法,會(huì)導(dǎo)致數(shù)據(jù)不一致等嚴(yán)重問(wèn)題,絕對(duì)不要使用。
  • 應(yīng)用場(chǎng)景: 更適合框架、應(yīng)用服務(wù)器、數(shù)據(jù)庫(kù)等底層系統(tǒng)或需要高可靠性的復(fù)雜系統(tǒng),它們有更完善的資源管理和恢復(fù)機(jī)制。普通應(yīng)用開(kāi)發(fā)更應(yīng)注重預(yù)防。

總結(jié)與建議

  1. 首選鎖順序化: 在設(shè)計(jì)多鎖交互時(shí),強(qiáng)制全局一致的鎖獲取順序是最有效且推薦的預(yù)防策略。
  2. 善用 Lock 和 tryLock: 當(dāng)鎖順序難以嚴(yán)格保證或需要更靈活控制時(shí),使用 ReentrantLock 及其 tryLock(帶超時(shí))方法,實(shí)現(xiàn)一次性申請(qǐng)所有鎖或鎖超時(shí)機(jī)制。務(wù)必在 finally 塊中釋放鎖。
  3. 良好的并發(fā)習(xí)慣:
    • 最小化鎖范圍(縮小 synchronized 塊)。
    • 優(yōu)先使用并發(fā)集合 (java.util.concurrent.*) 和原子變量。
    • 考慮不可變對(duì)象和線程本地存儲(chǔ) (ThreadLocal)。
  4. 避免嵌套鎖: 盡量避免在一個(gè)鎖保護(hù)的代碼塊內(nèi)再去獲取另一個(gè)鎖。如果必須,嚴(yán)格應(yīng)用鎖順序化。
  5. 超時(shí)機(jī)制: 在可能長(zhǎng)時(shí)間等待的地方(包括鎖獲取、條件等待 Condition.await、線程 join、Future.get 等)使用超時(shí)參數(shù),防止永久阻塞,給系統(tǒng)提供回退的機(jī)會(huì)。
  6. 工具檢測(cè): 利用 JConsole、VisualVM、jstack 命令行工具等定期檢查或在線診斷潛在的死鎖。jstack -l <pid> 輸出的線程轉(zhuǎn)儲(chǔ)會(huì)明確標(biāo)識(shí)出找到的死鎖和涉及的線程/鎖。

記住: 預(yù)防死鎖的關(guān)鍵在于設(shè)計(jì)和編碼階段就意識(shí)到風(fēng)險(xiǎn)并應(yīng)用上述策略。事后檢測(cè)和恢復(fù)往往是代價(jià)高昂的最后手段。??

到此這篇關(guān)于Java死鎖原因及預(yù)防方法的文章就介紹到這了,更多相關(guān)Java死鎖原因及預(yù)防內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • jdbcTemplate使用方法實(shí)例解析

    jdbcTemplate使用方法實(shí)例解析

    這篇文章主要介紹了jdbcTemplate使用方法實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • 解決pageHelper分頁(yè)失效以及如何配置問(wèn)題

    解決pageHelper分頁(yè)失效以及如何配置問(wèn)題

    這篇文章主要介紹了解決pageHelper分頁(yè)失效以及如何配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • 聊聊spring @Transactional 事務(wù)無(wú)法使用的可能原因

    聊聊spring @Transactional 事務(wù)無(wú)法使用的可能原因

    這篇文章主要介紹了spring @Transactional 事務(wù)無(wú)法使用的可能原因,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • mybatis-plus返回map自動(dòng)轉(zhuǎn)駝峰配置操作

    mybatis-plus返回map自動(dòng)轉(zhuǎn)駝峰配置操作

    這篇文章主要介紹了mybatis-plus返回map自動(dòng)轉(zhuǎn)駝峰配置操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-11-11
  • IDEA Maven Mybatis generator 自動(dòng)生成代碼(實(shí)例講解)

    IDEA Maven Mybatis generator 自動(dòng)生成代碼(實(shí)例講解)

    下面小編就為大家分享一篇IDEA Maven Mybatis generator 自動(dòng)生成代碼的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • SpringBoot時(shí)區(qū)問(wèn)題解決以及徹底解決時(shí)差問(wèn)題

    SpringBoot時(shí)區(qū)問(wèn)題解決以及徹底解決時(shí)差問(wèn)題

    這篇文章主要給大家介紹了關(guān)于SpringBoot時(shí)區(qū)問(wèn)題解決以及徹底解決時(shí)差問(wèn)題的相關(guān)資料,spring?boot作為微服務(wù)簡(jiǎn)易架構(gòu),擁有其自身的特點(diǎn),快速搭建架構(gòu),簡(jiǎn)單快捷,需要的朋友可以參考下
    2023-08-08
  • SpringCloud中FeignClient自定義配置

    SpringCloud中FeignClient自定義配置

    使用FeignClient時(shí)需了解其默認(rèn)配置機(jī)制,通過(guò)注入Decoder、Encoder等Bean實(shí)現(xiàn)自定義配置,下面就來(lái)介紹一下如何使用,感興趣的可以了解一下
    2025-08-08
  • Spring MVC Annotation驗(yàn)證的方法

    Spring MVC Annotation驗(yàn)證的方法

    這篇文章主要介紹了Spring MVC Annotation驗(yàn)證的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng)

    java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單汽車(chē)租賃系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • java多線程之定時(shí)器Timer的使用詳解

    java多線程之定時(shí)器Timer的使用詳解

    本篇文章主要介紹了java多線程之定時(shí)器Timer的使用詳解,Time類(lèi)主要負(fù)責(zé)完成定時(shí)計(jì)劃任務(wù)的功能,有興趣的可以了解一下。
    2017-04-04

最新評(píng)論