Java并发常见问题之死锁/活锁/饥饿的排查与解决方法

 更新时间:2026年03月05日 10:02:53   作者:C雨后彩虹  
Java死锁问题是多线程编程中一个经典且极具挑战性的并发控制问题,它直接影响程序的稳定性、性能和可维护性,这篇文章主要介绍了Java并发常见问题之死锁/活锁/饥饿的排查与解决方法的相关资料,需要的朋友可以参考下

一、前言

在多线程并发编程中,除了数据安全问题,线程协作异常是另一类高频问题,其中死锁、活锁、饥饿是最典型的三类问题。这些问题会导致线程无法正常执行、系统性能下降甚至服务不可用,且排查难度高 —— 死锁可能隐藏数月,在高并发场景下才会触发。

本文将深入剖析这三类问题的产生原因、典型场景、排查方法,并提供可落地的解决方案与避坑指南。

二、死锁(Deadlock)

1. 死锁的定义

死锁是指两个或多个线程互相持有对方所需的锁,且都不释放自己持有的锁,导致所有线程永久阻塞,无法继续执行的状态。

2. 死锁的四大必要条件(缺一不可)

只有同时满足以下 4 个条件,才会产生死锁:

  1. 互斥条件:锁资源只能被一个线程持有,其他线程无法获取;

  2. 持有并等待条件:线程持有已获取的锁,同时等待其他线程持有的锁;

  3. 不可剥夺条件:线程持有的锁不能被强制剥夺,只能由线程主动释放;

  4. 循环等待条件:线程 A 等待线程 B 的锁,线程 B 等待线程 A 的锁,形成循环等待链。

3. 死锁典型案例

/**
 * 死锁演示:线程1持有锁A,等待锁B;线程2持有锁B,等待锁A
 */
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) {
        // 线程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,执行完成");
                }
            }
        }, "线程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,执行完成");
                }
            }
        }, "线程2").start();
    }
}

运行结果 :两个线程互相等待对方的锁,永久阻塞,无后续输出。

4. 死锁的排查方法

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

1.执行 jps 命令,获取进程 ID:

jps
# 输出示例:1234 DeadlockDemo

2.执行 jstack <进程ID> ,查看线程状态:

jstack 1234

3.关键输出(死锁提示):

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. 启动 JConsole(JDK/bin 目录下),连接目标进程;

  2. 切换到「线程」标签页,点击「检测死锁」,自动识别死锁线程及锁信息。

5. 死锁的解决方案

核心思路: 破坏死锁的四大必要条件之一 ,常用方案:

方案 1:统一锁获取顺序(破坏循环等待条件)

所有线程按相同的顺序获取锁,避免循环等待:

// 优化后:线程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,执行完成");
        }
    }
}, "线程1").start();
new Thread(() -> {
    synchronized (LOCK_A) { // 统一先获取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,执行完成");
        }
    }
}, "线程2").start();

方案 2:使用可中断锁(破坏不可剥夺条件)

使用 ReentrantLock 的 lockInterruptibly() 方法,允许线程被中断,主动释放锁:

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,执行完成");
            } 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,执行完成");
            } 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秒,若检测到死锁,中断线程1
        Thread.sleep(3000);
        if (thread1.isAlive() && thread2.isAlive()) {
            thread1.interrupt();
            System.out.println("检测到死锁,中断线程1");
        }
    }
}

方案 3:使用超时获取锁(破坏持有并等待条件)

使用 ReentrantLock 的 tryLock(long time, TimeUnit unit) 方法,超时未获取锁则放弃,避免永久等待:

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)) { // 超时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,执行完成");
                        LOCK_B.unlock();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 超时未获取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,执行完成");
                        LOCK_A.unlock();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 超时未获取LOCK_A,释放LOCK_B");
                        LOCK_B.unlock();
                    }
                    LOCK_B.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程2").start();
    }
}

三、活锁(Livelock)

1. 活锁的定义

活锁是指线程没有阻塞,但因互相谦让(或重试),导致始终无法获取所需资源,程序无法推进的状态。与死锁的核心区别:死锁是线程完全阻塞,活锁是线程一直在执行,但无实际进展。

2. 活锁典型案例

/**
 * 活锁演示:两个线程互相释放锁,重试获取对方的锁,始终无法执行完成
 */
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,执行完成");
                            LOCK_B.unlock();
                            LOCK_A.unlock();
                            break; // 执行完成,退出循环
                        } 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,执行完成");
                            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();
    }
}

运行结果 :两个线程反复获取锁、释放锁、重试,始终无法同时获取两个锁,陷入无限循环。

3. 活锁的解决方案

核心思路: 打破 “同步重试” 的循环 ,常用方案:

1.随机重试延迟:每个线程重试时使用随机的休眠时间,避免同步谦让;

// 替换固定休眠时间为随机时间
Thread.sleep(new Random().nextInt(500));

2.优先级机制:为线程设置不同的优先级,让部分线程优先获取资源;

3.限制重试次数:设置最大重试次数,超过次数则放弃并报警,避免无限循环。

四、饥饿(Starvation)

1. 饥饿的定义

饥饿是指某些线程因优先级低、或始终竞争不到锁资源,导致长期无法执行的状态。例如:高优先级线程持续占用 CPU,低优先级线程始终无法执行;非公平锁下,某些线程始终抢不到锁。

2. 饥饿典型案例

/**
 * 饥饿演示:高优先级线程持续占用锁,低优先级线程长期无法获取锁
 */
public class StarvationDemo {
    private static final Object LOCK = new Object();
    public static void main(String[] args) {
        // 低优先级线程
        Thread lowPriorityThread = new Thread(() -> {
            int count = 0;
            while (true) {
                synchronized (LOCK) {
                    System.out.println(Thread.currentThread().getName() + " 执行第" + (++count) + "次");
                    try {
                        Thread.sleep(100); // 持有锁时间短,但仍被高优先级线程抢占
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "低优先级线程");
        lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 优先级1
        // 高优先级线程
        Thread highPriorityThread = new Thread(() -> {
            int count = 0;
            while (true) {
                synchronized (LOCK) {
                    System.out.println(Thread.currentThread().getName() + " 执行第" + (++count) + "次");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "高优先级线程");
        highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 优先级10
        lowPriorityThread.start();
        highPriorityThread.start();
    }
}

运行结果 :高优先级线程的执行次数远多于低优先级线程,低优先级线程长期 “饥饿”。

3. 饥饿的解决方案

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

1.使用公平锁: ReentrantLock 的公平锁模式保证线程按 FIFO 顺序获取锁,避免插队;

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

2.避免线程优先级差异:尽量将线程优先级设置为相同(默认 5),减少调度器的偏好;

3.减少锁持有时间:缩短同步代码块的执行时间,让锁尽快释放,增加低优先级线程的获取机会;

4.使用线程池:线程池的工作线程优先级一致,且有任务队列缓冲,避免个别线程长期抢占资源。

五、并发问题对比

问题类型

核心特征

线程状态

排查难度

核心解决方案

死锁

互相持有锁,永久阻塞

BLOCKED

中(jstack 可直接检测)

统一锁顺序、超时获取、可中断锁

活锁

无阻塞,但互相谦让,无进展

RUNNABLE

高(无明显报错,需分析日志)

随机重试延迟、优先级机制、限制重试次数

饥饿

长期竞争不到资源,偶尔执行

RUNNABLE

中(需统计执行频率)

公平锁、统一优先级、减少锁持有时间

六、实战避坑指南

1. 预防死锁的最佳实践

  • 最小化锁范围:仅在必要的代码块加锁,缩短锁持有时间;

  • 避免嵌套锁:尽量不使用多层锁嵌套,若必须使用,严格统一锁获取顺序;

  • 使用定时锁:优先使用 tryLock(timeout) 替代无超时的锁获取;

  • 监控锁状态:通过 JMX/APM 工具监控锁的持有时间、竞争次数,提前发现死锁风险。

2. 通用优化建议

  • 优先使用并发工具: ConcurrentHashMap 、 CountDownLatch 等工具已封装安全的并发逻辑,避免手动加锁;

  • 避免手动线程管理:使用线程池( ThreadPoolExecutor )替代手动创建线程,统一管理线程生命周期;

  • 增加容错机制:关键业务线程设置超时、重试、降级逻辑,避免因并发问题导致服务不可用;

  • 压测验证:上线前通过高并发压测,模拟极端场景,提前暴露死锁 / 活锁 / 饥饿问题。

七、总结

本文深入剖析了 Java 并发编程中死锁、活锁、饥饿三类典型问题的产生原因、典型场景与解决方案。死锁是最致命的问题,需通过破坏四大必要条件来预防;活锁需打破同步重试的循环;饥饿需保证资源分配的公平性。在实际开发中,应遵循 “预防大于排查” 的原则:通过统一锁顺序、使用公平锁、缩短锁持有时间等手段,从根源减少并发问题的发生;同时掌握 jstack、JConsole 等排查工具,快速定位已出现的问题。

到此这篇关于Java并发常见问题之死锁/活锁/饥饿的排查与解决方法的文章就介绍到这了,更多相关Java并发死锁/活锁/饥饿内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java线程状态转换的详细过程

    Java线程状态转换的详细过程

    Java线程状态转换是Java多线程编程中的关键概念,对于理解和优化并发程序至关重要,Java线程在其生命周期中经历多种状态,这些状态之间的转换是由线程调度器根据特定的策略来决定的,以下是对Java线程状态转换的详细说明,需要的朋友可以参考下
    2025-09-09
  • Java如何实现字符串补齐

    Java如何实现字符串补齐

    这篇文章主要介绍了Java如何实现字符串补齐的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Java数据结构之链表实现(单向、双向链表及链表反转)

    Java数据结构之链表实现(单向、双向链表及链表反转)

    这篇文章主要给大家介绍了关于Java数据结构之链表实现的相关资料,其中包括单向链表、双向链表及链表反转的实现代码,需要的朋友可以参考下
    2021-06-06
  • java SpringMvc中拦截器的应用

    java SpringMvc中拦截器的应用

    大家好,本篇文章主要讲的是java SpringMvc中拦截器的应用,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • Java经典面试题最全汇总208道(二)

    Java经典面试题最全汇总208道(二)

    这篇文章主要介绍了Java经典面试题最全汇总208道(二),本文章内容详细,该模块分为了六个部分,本次为第二部分,需要的朋友可以参考下<BR>
    2023-01-01
  • 不看后悔!揭秘游戏服务器开发

    不看后悔!揭秘游戏服务器开发

    刚开始时以为做游戏服务器和做web差不多,但是经过一段时间之后,才发现代码太多,太乱了,这里我把一些游戏开发方面的东西整理一下,希望能对那些想做游戏服务器开发的朋友有所帮助
    2021-06-06
  • Spring中SmartLifecycle和Lifecycle的作用和区别

    Spring中SmartLifecycle和Lifecycle的作用和区别

    这篇文章主要介绍了Spring中SmartLifecycle和Lifecycle的作用和区别,本文通过实例代码给大家介绍的非常详细对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Spring整合Mycat2的具体过程详解

    Spring整合Mycat2的具体过程详解

    这篇文章主要给大家介绍Springboot整合Mycat2的具体过程,文中有详细的图解过程,感兴趣的小伙伴可以跟着小编一起来学习
    2023-05-05
  • Java实战员工绩效管理系统的实现流程

    Java实战员工绩效管理系统的实现流程

    只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+Mysql+Maven+HTML实现一个员工绩效管理系统,大家可以在过程中查缺补漏,提升水平
    2022-01-01
  • Springmvc国际化自动配置代码实现

    Springmvc国际化自动配置代码实现

    这篇文章主要介绍了Springmvc国际化自动配置代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04

最新评论