Java死锁详解:手把手演示死锁产生、原理及解决方案 Java死锁详解手把手演示死锁产生、原理及解决方案一、什么是Java死锁在Java多线程并发编程中**死锁Dead Lock**是高频且致命的线程安全问题。简单来说两个或多个线程互相持有对方需要的锁资源同时互相等待对方释放锁资源且双方都永不主动释放自己的锁导致所有线程永久阻塞程序卡死无法继续执行。死锁不会抛出异常、不会崩溃只会让程序静默卡死是线上生产环境中最难排查的问题之一。二、完整死锁演示案例下面我将提供一段可直接运行的Java死锁代码模拟最经典的两线程、两资源互持互等死锁场景也是面试最常考的死锁案例。2.1 演示代码/** * Java 死锁经典演示案例 * 场景两个线程、两把锁互相持有对方所需锁互相等待 * author 博客作者 * date 2026 */publicclassDeadLockDemo{// 定义两个独立的共享锁资源privatestaticfinalObjectresource1newObject();privatestaticfinalObjectresource2newObject();publicstaticvoidmain(String[]args){// 线程1先抢占resource1锁再尝试抢占resource2锁newThread(()-{synchronized(resource1){System.out.println(Thread.currentThread().getName() 成功获取【resource1】锁);try{// 休眠1秒保证两个线程都能各自抢占到第一把锁Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName() 等待获取【resource2】锁...);// 尝试获取被线程2持有的resource2锁进入永久等待synchronized(resource2){System.out.println(Thread.currentThread().getName() 成功获取【resource2】锁);}}},线程 1).start();// 线程2先抢占resource2锁再尝试抢占resource1锁newThread(()-{synchronized(resource2){System.out.println(Thread.currentThread().getName() 成功获取【resource2】锁);try{// 休眠1秒保证两个线程都能各自抢占到第一把锁Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName() 等待获取【resource1】锁...);// 尝试获取被线程1持有的resource1锁进入永久等待synchronized(resource1){System.out.println(Thread.currentThread().getName() 成功获取【resource1】锁);}}},线程 2).start();}}2.2 运行结果运行代码后控制台输出如下程序不会结束一直处于运行卡死状态线程 1 成功获取【resource1】锁 线程 2 成功获取【resource2】锁 线程 1 等待获取【resource2】锁... 线程 2 等待获取【resource1】锁...可以看到两个线程都卡在等待锁的步骤后续获取锁的日志永远不会打印程序永久阻塞死锁产生。三、代码原理深度解析3.1 执行流程拆解线程1启动通过synchronized (resource1)成功抢占resource1锁持有锁不释放。线程2启动通过synchronized (resource2)成功抢占resource2锁持有锁不释放。sleep休眠作用两个线程休眠1秒核心目的是保证两个线程都能成功拿到各自的第一把锁避免单线程快速执行完释放锁无法复现死锁。死锁触发线程1持有resource1想要获取线程2手中的resource2获取不到进入阻塞等待线程2持有resource2想要获取线程1手中的resource1获取不到进入阻塞等待。双方都持有对方需要的锁且都不释放互相永久等待死锁形成。3.2 死锁产生的四大必要条件必考Java死锁的产生必须同时满足以下4个条件缺一不可这也是解决死锁的核心依据互斥条件锁资源同一时刻只能被一个线程持有其他线程必须等待synchronized锁天然满足。请求与保持条件一个线程已经持有一把锁同时请求获取另一把被占用的锁且不释放已持有的锁。不可剥夺条件锁资源只能被持有者主动释放其他线程无法强制抢占、剥夺锁。循环等待条件线程之间形成闭环等待链路线程1等线程2、线程2等线程1。四、如何验证程序发生了死锁除了观察控制台卡死我们可以通过JDK自带工具精准检测死锁适合线上问题排查。4.1 jps 查看进程号打开命令行输入jps找到当前Java程序的进程PID。4.2 jstack 检测死锁执行命令jstack 进程PID在输出日志末尾可以看到明确的死锁提示Found one Java-level deadlock并清晰展示两个线程的锁持有、等待情况。五、死锁解决方案根据死锁四大必要条件只要破坏其中任意一个条件即可避免死锁。日常开发中最常用、最简单的方案是破坏循环等待条件。5.1 统一锁的获取顺序最优方案让所有线程获取锁的顺序保持一致比如所有线程必须先获取resource1再获取resource2彻底杜绝循环等待。优化后核心代码// 线程1、线程2 锁顺序完全统一newThread(()-{synchronized(resource1){System.out.println(Thread.currentThread().getName() 成功获取【resource1】锁);try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(resource2){System.out.println(Thread.currentThread().getName() 成功获取【resource2】锁);}}},线程 1).start();newThread(()-{// 统一锁顺序先resource1 后 resource2synchronized(resource1){System.out.println(Thread.currentThread().getName() 成功获取【resource1】锁);try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(resource2){System.out.println(Thread.currentThread().getName() 成功获取【resource2】锁);}}},线程 2).start();优化后线程2不会反向抢锁不会形成循环等待死锁彻底消失。5.2 其他解决方案破坏请求与保持线程一次性申请所有需要的锁不持有部分锁再请求新锁。使用可中断锁Lock放弃synchronized使用ReentrantLock可通过lockInterruptibly()中断等待锁的线程打破死锁。设置锁超时时间线程等待锁超时后自动放弃请求释放已持有锁。六、总结死锁核心成因多线程交叉持有锁、循环互相等待死锁四大必要条件互斥、请求保持、不可剥夺、循环等待解决死锁核心思路破坏任意一个必要条件优先使用「统一锁顺序」方案简单高效、无性能损耗线上排查死锁优先使用jstack工具快速定位死锁线程和锁资源。我是专注Java后端干货分享的博主后续会持续更新多线程、JVM、并发源码等核心知识欢迎点赞收藏关注