亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

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

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

死鎖的發(fā)生需要同時(shí)滿足以下四個(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é)家就餐問題簡(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();
    }
}

分析死鎖條件滿足情況

  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、按名稱排序等)。
    • 在任何需要獲取多個(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) 作為最后手段來排序,但要注意哈希沖突。

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

  • 原理: 一個(gè)線程在開始執(zhí)行任務(wù)前,一次性申請(qǐng)它所需的所有鎖。如果無法一次性獲取全部鎖,它就不持有任何已獲得的鎖(全部釋放),等待一段時(shí)間再重試或采用其他策略。
  • 實(shí)現(xiàn):
    • 設(shè)計(jì)一個(gè)獲取多個(gè)鎖的機(jī)制(例如,一個(gè)包含所有需要鎖的集合)。
    • 嘗試一次性獲取集合中所有的鎖(通常使用 tryLock)。
    • 如果成功獲取所有鎖,執(zhí)行任務(wù)。
    • 如果獲取任何一個(gè)鎖失敗(超時(shí)或立即失?。?,則釋放它已經(jīng)成功獲取的所有鎖,然后進(jìn)行回退(等待、重試、放棄任務(wù)等)。
  • 效果: 線程要么同時(shí)持有所有需要的鎖(不等待),要么不持有任何鎖(不保持部分鎖去等待其他鎖),破壞了“持有并等待”。
  • 工具: Java 的 Lock 接口(特別是 ReentrantLock)提供了 tryLock() 方法(可帶超時(shí))來實(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í)避免無限等待)
                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 {
                // 無論如何,在退出前確保釋放已獲得的鎖
                if (gotSecond) secondLock.unlock();
                if (gotFirst) firstLock.unlock();
            }
            // 如果沒能一次性獲得兩個(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ù)真正需要互斥訪問的共享數(shù)據(jù)操作。不要在鎖內(nèi)執(zhí)行耗時(shí)操作(如IO)。
    • 使用線程安全類: 優(yōu)先使用 ConcurrentHashMap, CopyOnWriteArrayList, AtomicInteger 等并發(fā)容器和原子類,它們內(nèi)部實(shí)現(xiàn)了高效的并發(fā)控制,減少了你顯式加鎖的需要。
    • 不可變對(duì)象: 使用不可變對(duì)象(final 字段,構(gòu)造后狀態(tài)不變)。訪問不可變對(duì)象不需要同步。
    • 線程本地存儲(chǔ): 使用 ThreadLocal 為每個(gè)線程創(chuàng)建變量的副本,避免共享。
  • 效果: 雖然不是直接破壞必要條件,但這是良好的并發(fā)編程實(shí)踐,能顯著降低死鎖發(fā)生的概率和影響范圍。

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

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

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

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

記?。?/strong> 預(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)文章

  • Java枚舉實(shí)現(xiàn)自增賦值的方法

    Java枚舉實(shí)現(xiàn)自增賦值的方法

    在Java編程里,枚舉(enum)其實(shí)是一種特別的類型,用來表示一組常量,當(dāng)我們開發(fā)程序的時(shí)候,常常需要給這些枚舉加點(diǎn)其他功能,比如自增賦值的方法,這樣就能更方便地管理和使用啦,這篇文章和大家聊聊,怎么在Java中實(shí)現(xiàn)枚舉的自增賦值
    2025-04-04
  • Spring中的@Qualifier注解和@Resource注解區(qū)別解析

    Spring中的@Qualifier注解和@Resource注解區(qū)別解析

    這篇文章主要介紹了Spring中的@Qualifier注解和@Resource注解區(qū)別解析,@Qualifier注解的用處是當(dāng)一個(gè)接口有多個(gè)實(shí)現(xiàn)的時(shí)候,為了指名具體調(diào)用哪個(gè)類的實(shí)現(xiàn),@Resource注解可以通過 byName命名和byType類型的方式注入,需要的朋友可以參考下
    2023-11-11
  • Java mysql詳細(xì)講解雙數(shù)據(jù)源配置使用

    Java mysql詳細(xì)講解雙數(shù)據(jù)源配置使用

    在開發(fā)過程中我們常常會(huì)用到兩個(gè)數(shù)據(jù)庫(kù),一個(gè)數(shù)據(jù)用來實(shí)現(xiàn)一些常規(guī)的增刪改查,另外一個(gè)數(shù)據(jù)庫(kù)用來實(shí)時(shí)存數(shù)據(jù)。進(jìn)行數(shù)據(jù)的統(tǒng)計(jì)分析??梢宰x寫分離。可以更好的優(yōu)化和提高效率;或者兩個(gè)數(shù)據(jù)存在業(yè)務(wù)分離的時(shí)候也需要多個(gè)數(shù)據(jù)源來實(shí)現(xiàn)
    2022-06-06
  • Java中ArrayList和LinkedList之間的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java中ArrayList和LinkedList之間的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了Java中ArrayList和LinkedList之間的區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • SpringBoot+Hutool+thymeleaf完成導(dǎo)出Excel的實(shí)現(xiàn)方法

    SpringBoot+Hutool+thymeleaf完成導(dǎo)出Excel的實(shí)現(xiàn)方法

    這篇文章主要介紹了SpringBoot+Hutool+thymeleaf完成導(dǎo)出Excel,本篇示例當(dāng)中不僅僅有后端,而且還提供了前端html,html當(dāng)中利用js將后端 輸出流直接下載為文件,需要的朋友可以參考下
    2022-03-03
  • SpringBoot配置Redis實(shí)現(xiàn)保存獲取和刪除數(shù)據(jù)

    SpringBoot配置Redis實(shí)現(xiàn)保存獲取和刪除數(shù)據(jù)

    本文主要介紹了SpringBoot配置Redis實(shí)現(xiàn)保存獲取和刪除數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端

    java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端

    本文主要介紹了java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Mybatis?sql與xml文件讀取方法詳細(xì)分析

    Mybatis?sql與xml文件讀取方法詳細(xì)分析

    這篇文章主要介紹了Mybatis?sql與xml文件讀取方法,在執(zhí)行一個(gè)自定義sql語句時(shí),dao對(duì)應(yīng)的代理對(duì)象時(shí)如何找到sql,也就是dao的代理對(duì)象和sql之間的關(guān)聯(lián)關(guān)系是如何建立的
    2023-01-01
  • 使用@CachePut?更新數(shù)據(jù)庫(kù)和更新緩存

    使用@CachePut?更新數(shù)據(jù)庫(kù)和更新緩存

    這篇文章主要介紹了使用@CachePut?更新數(shù)據(jù)庫(kù)和更新緩存方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Spring線程池ThreadPoolTaskExecutor的用法及說明

    Spring線程池ThreadPoolTaskExecutor的用法及說明

    這篇文章主要介紹了Spring線程池ThreadPoolTaskExecutor的用法及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07

最新評(píng)論