Java 死锁恢复(线程中断 + 资源释放)

林欢喜 Java经验 发布时间:2025-12-25 11:28:58 阅读数:15022 1
下文笔者讲述java中死锁恢复的方法及示例分享,如下所示

死锁恢复的前置条件

Java 中死锁的本质是
    多个线程互相持有对方所需的锁,
     且均处于无限阻塞状态(BLOCKED/WAITING)
      线程无法主动感知死锁
      也无法自行释放已持有的锁

基于线程中断 + 资源释放的恢复方案,核心前提是:
   1.线程在等待锁(或其他资源)时,
        需支持响应中断(避免线程忽略中断信号,无法触发后续资源释放)
   2.需提前设计
        锁资源的释放逻辑
         (保线程收到中断信号后,能主动释放已持有的锁,打破死锁依赖链)
   3.需先通过前文提到的 jstack、Arthas 等工具
        精准定位死锁线程和锁关系
         (明确需要中断哪些线程、释放哪些资源)

死锁恢复的方法汇总

 
Java 死锁恢复(线程中断 + 资源释放)的核心要点:
1.  核心逻辑:中断死锁线程 → 线程响应中断(抛出 `InterruptedException`)→
        `finally` 块释放已持有的锁 → 打破死锁依赖;
2.  核心技术:依赖 `Lock` 接口的 `lockInterruptibly()`
      (可中断锁)和 `tryLock()`(超时锁),`synchronized` 锁不支持该方案;
3.  关键保障:资源释放必须放在 `finally` 块,
       遵循「反向获取顺序」释放锁;
4.  实践原则:优先通过「统一锁顺序、减少锁粒度」预防死锁,
      线上死锁可通过 Arthas 快速中断恢复,无法恢复时优雅重启应用。


线程回复方法

方案1:基于可中断锁(`Lock` 接口)的恢复

`synchronized` 关键字修饰的锁是不可中断的
      (线程等待 `synchronized` 锁时,无法通过中断唤醒),
      而`java.util.concurrent.locks.Lock` 接口提供了可中断的锁获取方法(`lockInterruptibly()`)
       是实现死锁恢复的核心技术。

核心步骤:
   1.使用 `ReentrantLock` 等实现类替代 `synchronized`,作为锁资源;
   2.线程通过 `lockInterruptibly()` 方法获取锁
         (该方法会响应中断,若线程在等待锁时被中断,会抛出 `InterruptedException`);
   3.捕获 `InterruptedException` 异常,
         在异常处理块中释放已持有的其他锁资源;
   4.定位死锁线程后,调用 `Thread.interrupt()` 发送中断信号,
        触发线程响应并释放资源。

示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/
 * 基于可中断锁的死锁恢复示例
 */
public class InterruptibleLockDeadLockRecovery {
    // 定义两个可中断锁资源
    private static final Lock LOCK_A = new ReentrantLock();
    private static final Lock LOCK_B = new ReentrantLock();

    // 线程1:尝试先获取LOCK_A,再获取LOCK_B
    private static class Thread1 extends Thread {
        @Override
        public void run() {
            boolean acquiredA = false;
            boolean acquiredB = false;
            try {
                // 可中断方式获取锁A:响应中断信号
                LOCK_A.lockInterruptibly();
                acquiredA = true;
                System.out.println(Thread.currentThread().getName() + " 已获取锁A,等待获取锁B");
                
                // 模拟业务延迟,触发死锁
                Thread.sleep(1000);
                
                // 可中断方式获取锁B:响应中断信号
                LOCK_B.lockInterruptibly();
                acquiredB = true;
                System.out.println(Thread.currentThread().getName() + " 已获取锁B,执行业务逻辑");

            } catch (InterruptedException e) {
                // 响应中断:打印中断信息,准备释放已持有的锁
                System.out.println(Thread.currentThread().getName() + " 收到中断信号,即将释放已持有的锁");
                Thread.currentThread().interrupt(); // 保留中断标志位(可选)
            } finally {
                // 资源释放:仅释放已成功获取的锁,避免非法释放异常
                if (acquiredB) {
                    LOCK_B.unlock();
                    System.out.println(Thread.currentThread().getName() + " 释放锁B");
                }
                if (acquiredA) {
                    LOCK_A.unlock();
                    System.out.println(Thread.currentThread().getName() + " 释放锁A");
                }
            }
        }
    }

    // 线程2:尝试先获取LOCK_B,再获取LOCK_A(与线程1锁顺序相反,触发死锁)
    private static class Thread2 extends Thread {
        @Override
        public void run() {
            boolean acquiredB = false;
            boolean acquiredA = false;
            try {
                // 可中断方式获取锁B
                LOCK_B.lockInterruptibly();
                acquiredB = true;
                System.out.println(Thread.currentThread().getName() + " 已获取锁B,等待获取锁A");
                
                // 模拟业务延迟,触发死锁
                Thread.sleep(1000);
                
                // 可中断方式获取锁A
                LOCK_A.lockInterruptibly();
                acquiredA = true;
                System.out.println(Thread.currentThread().getName() + " 已获取锁A,执行业务逻辑");

            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 收到中断信号,即将释放已持有的锁");
                Thread.currentThread().interrupt();
            } finally {
                // 按反向顺序释放已获取的锁
                if (acquiredA) {
                    LOCK_A.unlock();
                    System.out.println(Thread.currentThread().getName() + " 释放锁A");
                }
                if (acquiredB) {
                    LOCK_B.unlock();
                    System.out.println(Thread.currentThread().getName() + " 释放锁B");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread1();
        Thread t2 = new Thread2();
        t1.setName("死锁线程-1");
        t2.setName("死锁线程-2");

        // 启动线程,触发死锁
        t1.start();
        t2.start();
        
        // 等待3秒,确认死锁形成
        Thread.sleep(3000);
        System.out.println("===== 检测到死锁,开始执行中断恢复 =====");

        // 中断其中一个死锁线程(打破死锁依赖)
        t1.interrupt();

        // 等待线程执行完毕,观察资源释放情况
        t1.join();
        t2.join();
        System.out.println("===== 死锁恢复完成,所有锁资源已释放 =====");
    }
}

代码说明

运行结果说明:
  1.线程1和线程2启动后,因锁获取顺序相反形成死锁;
  2.主线程3秒后发送中断信号给线程1;
  3.线程1响应中断,进入 `InterruptedException` 异常块,
       最终在 `finally` 中释放已持有的锁A;
  4.线程2获取到锁A,执行完业务逻辑后释放锁A和锁B,死锁被打破。

方案2:基于超时锁(`tryLock()`)的恢复(无需主动中断)

`Lock.tryLock(long time, TimeUnit unit)` 方法支持设置锁获取超时时间,
     若超时未获取到锁,会返回 `false`,线程可主动释放已持有的锁,
      避免进入死锁状态(本质是「预防+恢复」结合,提前规避死锁,也可用于已发生死锁的场景)。

核心步骤:
  1.使用 `tryLock()` 方法设置超时时间,尝试获取锁;
  2.若超时未获取到锁,直接返回,在 `finally` 中释放已持有的其他锁;
  3.可选:配合线程中断,增强灵活性。

代码示例

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/
 * 基于超时锁的死锁恢复(规避)示例
 */
public class TimeoutLockDeadLockRecovery {
    private static final Lock LOCK_A = new ReentrantLock();
    private static final Lock LOCK_B = new ReentrantLock();

    private static class BusinessThread extends Thread {
        private final String threadName;
        private final Lock firstLock;
        private final Lock secondLock;

        public BusinessThread(String threadName, Lock firstLock, Lock secondLock) {
            this.threadName = threadName;
            this.firstLock = firstLock;
            this.secondLock = secondLock;
        }

        @Override
        public void run() {
            boolean acquiredFirst = false;
            boolean acquiredSecond = false;
            try {
                // 尝试获取第一个锁(立即获取)
                acquiredFirst = firstLock.tryLock(0, TimeUnit.MILLISECONDS);
                if (!acquiredFirst) {
                    System.out.println(threadName + " 未获取到第一个锁,直接退出");
                    return;
                }
                System.out.println(threadName + " 已获取第一个锁,等待获取第二个锁");

                // 尝试获取第二个锁,设置3秒超时
                acquiredSecond = secondLock.tryLock(3, TimeUnit.SECONDS);
                if (!acquiredSecond) {
                    System.out.println(threadName + " 获取第二个锁超时,即将释放第一个锁");
                    return;
                }
                System.out.println(threadName + " 已获取第二个锁,执行业务逻辑");

            } catch (InterruptedException e) {
                System.out.println(threadName + " 被中断,即将释放已持有的锁");
                Thread.currentThread().interrupt();
            } finally {
                // 释放已获取的锁
                if (acquiredSecond) {
                    secondLock.unlock();
                    System.out.println(threadName + " 释放第二个锁");
                }
                if (acquiredFirst) {
                    firstLock.unlock();
                    System.out.println(threadName + " 释放第一个锁");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 线程1:先获取A,再获取B
        BusinessThread t1 = new BusinessThread("业务线程-1", LOCK_A, LOCK_B);
        // 线程2:先获取B,再获取A(易死锁)
        BusinessThread t2 = new BusinessThread("业务线程-2", LOCK_B, LOCK_A);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("===== 执行完成,无死锁残留 =====");
    }
}
代码运行结果说明
   1.线程1和线程2分别获取锁A和锁B后,
       互相等待对方的锁;
   2.3秒后,其中一个线程获取第二个锁超时,
       返回并释放已持有的锁;
   3.另一个线程成功获取第二个锁,执行完业务后释放所有锁,
      避免死锁持续。

方案3:基于线程状态检测的主动中断恢复(线上实战)

针对已发生死锁的线上应用,需先通过工具定位死锁线程,
再通过线程 ID 找到对应的 `Thread` 对象(或通过线程池管理线程),
主动发送中断信号实现恢复。

核心步骤:
1.  通过 `jstack`/`Arthas` 定位死锁线程的 ID(10进制)和线程名称;
2.  在应用中维护线程管理容器(如 `Map<Long, Thread>`),或通过线程池获取活跃线程;
3.  根据线程 ID/名称找到目标线程,调用 `interrupt()` 中断;
4.  线程响应中断后,释放已持有的锁资源

代码示例

(线程管理+主动中断)

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/
 * 基于线程管理+主动中断的线上死锁恢复示例
 */
public class ThreadManagerDeadLockRecovery {
    // 线程管理容器:存储线程ID与Thread对象的映射
    private static final Map<Long, Thread> THREAD_MAP = new HashMap<>();
    private static final Lock LOCK_A = new ReentrantLock();
    private static final Lock LOCK_B = new ReentrantLock();

    // 注册线程到管理容器
    private static void registerThread(Thread thread) {
        THREAD_MAP.put(thread.getId(), thread);
    }

    // 根据线程ID获取线程
    private static Thread getThreadById(long threadId) {
        return THREAD_MAP.get(threadId);
    }

    // 死锁线程任务
    private static class DeadLockTask implements Runnable {
        private final Lock firstLock;
        private final Lock secondLock;

        public DeadLockTask(Lock firstLock, Lock secondLock) {
            this.firstLock = firstLock;
            this.secondLock = secondLock;
        }

        @Override
        public void run() {
            Thread currentThread = Thread.currentThread();
            boolean acquiredFirst = false;
            boolean acquiredSecond = false;

            try {
                // 可中断方式获取锁
                firstLock.lockInterruptibly();
                acquiredFirst = true;
                System.out.println(currentThread.getName() + " 已获取第一个锁,线程ID:" + currentThread.getId());

                Thread.sleep(1000); // 模拟业务延迟,触发死锁

                secondLock.lockInterruptibly();
                acquiredSecond = true;
                System.out.println(currentThread.getName() + " 已获取第二个锁,执行业务逻辑");

            } catch (InterruptedException e) {
                System.out.println(currentThread.getName() + " 收到中断信号,线程ID:" + currentThread.getId());
                Thread.currentThread().interrupt();
            } finally {
                // 释放锁资源
                if (acquiredSecond) {
                    secondLock.unlock();
                    System.out.println(currentThread.getName() + " 释放第二个锁");
                }
                if (acquiredFirst) {
                    firstLock.unlock();
                    System.out.println(currentThread.getName() + " 释放第一个锁");
                }
                // 从管理容器中移除线程
                THREAD_MAP.remove(currentThread.getId());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 创建并注册线程1
        Thread t1 = new Thread(new DeadLockTask(LOCK_A, LOCK_B), "死锁线程-1");
        registerThread(t1);
        // 创建并注册线程2
        Thread t2 = new Thread(new DeadLockTask(LOCK_B, LOCK_A), "死锁线程-2");
        registerThread(t2);

        // 启动线程,触发死锁
        t1.start();
        t2.start();

        // 等待3秒,确认死锁形成
        Thread.sleep(3000);
        System.out.println("===== 线上检测到死锁,开始定位并中断 =====");

        // 步骤1:通过jstack/Arthas获取死锁线程ID(此处模拟,实际需工具定位)
        long deadLockThreadId1 = t1.getId();
        System.out.println("定位到死锁线程ID:" + deadLockThreadId1);

        // 步骤2:根据线程ID获取线程,发送中断信号
        Thread targetThread = getThreadById(deadLockThreadId1);
        if (targetThread != null) {
            targetThread.interrupt();
            System.out.println("已向线程ID:" + deadLockThreadId1 + " 发送中断信号");
        }

        // 等待线程执行完毕
        t1.join();
        t2.join();
        System.out.println("===== 线上死锁恢复完成 =====");
    }
}

注意事项

1. `synchronized` 锁无法通过中断实现恢复
`synchronized` 是不可中断的同步机制,线程在等待 `synchronized` 锁时,
    会忽略 `interrupt()` 中断信号,无法触发异常或退出等待状态。
    因此,死锁恢复方案仅适用于 `java.util.concurrent.locks` 包下的可中断锁。

2. 资源释放必须放在 `finally` 块中
无论线程是否被中断、是否发生异常,`finally` 块都会执行,
    确保已获取的锁资源能被释放,避免锁泄漏(若锁未释放,
   会导致其他线程无法获取锁,引发新的阻塞问题)。

3. 锁释放需遵循「反向获取顺序」
    获取锁时按「锁A→锁B」的顺序,释放时需按「锁B→锁A」的反向顺序,
     避免因释放顺序错误导致的异常或资源混乱。

4. 中断标志位的处理
    线程响应中断后,`InterruptedException` 会清除中断标志位,
       若需要后续逻辑感知中断状态,
      可在异常块中调用 `Thread.currentThread().interrupt()` 重新设置标志位。

5. 避免盲目中断所有死锁线程
     通常只需中断其中一个死锁线程,即可打破死锁依赖链;
      若中断所有线程,可能导致业务逻辑中断、数据不一致等问题,
      需结合业务场景选择中断目标。

 四、死锁恢复的局限性与最佳实践
 1. 局限性
- 恢复具有滞后性:需先检测到死锁,才能执行恢复操作,期间应用已处于异常状态;
- 业务影响风险:中断线程可能导致业务逻辑中断、数据不完整(如数据库事务未提交、缓存不一致);
- 仅适用于可中断锁:对 `synchronized` 锁引发的死锁,无法通过中断恢复,只能重启应用;
- 复杂场景难以覆盖:分布式死锁、多资源交叉死锁等场景,单纯的线程中断无法实现完整恢复。

2. 最佳实践(优先预防,其次恢复)
死锁恢复是「事后补救」手段,更高效的方式是提前预防死锁,结合按需恢复:
1.  统一锁获取顺序:所有线程按相同的顺序获取锁(如按锁对象的哈希值排序),从根源避免死锁;
2.  使用可中断/超时锁:优先使用 `lockInterruptibly()` 和 `tryLock()`,提前规避死锁;
3.  减少锁粒度:拆分全局锁为局部锁,避免不必要的锁竞争;
4.  线上监控告警:通过 Arthas、Prometheus + Grafana 实时监控线程状态,提前预警死锁;
5.  紧急恢复预案:对 `synchronized` 锁引发的死锁,或复杂分布式死锁,最可靠的方式是优雅重启应用(需确保应用具备故障转移、数据恢复能力)。
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaJingYan/202512/17666333718525.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者