Java 死锁怎么排查

在日常开发中,你可能遇到过这样的情况:程序跑着跑着突然卡住,日志里也没报错,重启后又能正常运行,但过段时间还会犯。这大概率是碰上了Java的死锁问题。简单说,死锁就是两个线程互相占着对方需要的资源,谁都不肯放手,最后都卡在那里动弹不得。

先看懂死锁的“形成逻辑”

要排查死锁,得先知道它怎么来的。举个生活化的例子:你和朋友去餐厅吃饭,你拿了筷子,朋友拿了勺子,你说“你把勺子给我,我就把筷子给你”,朋友说“你把筷子给我,我就把勺子给你”,结果俩人都等着对方先让步,饭也吃不成。

Java 死锁怎么排查

Java里的死锁也是类似,得同时满足四个条件:一是互斥(一个资源只能被一个线程用),二是占有并等待(线程拿着自己的资源,还等着别的资源),三是不可剥夺(不能强行抢别的线程的资源),四是循环等待(两个以上线程形成互相等待的环形)。只要打破其中一个,死锁就不会发生,但开发中难免踩坑,所以得会排查。

最实用的3种排查方法

排查死锁不用复杂工具,普通开发环境就能搞定,这几个方法亲测好用:

用JDK自带工具快速定位

JDK里有个jps命令,先输jps就能列出所有Java进程,找到你要查的应用进程ID。再用jstack命令,比如jstack 进程ID,输出的日志里会直接标“Found one Java-level deadlock”,还会告诉你哪两个线程死锁了,它们占了什么资源,卡在哪个代码行。

举个例子,输出里会写“Thread-1 waiting for monitor entry (0x000000076b5d2000), held by Thread-2”,一看就知道Thread-1在等Thread-2占的资源,Thread-2又在等Thread-1的资源,直接找到问题点。

日志里找“异常等待”的线索

如果没用到jstack,也能从日志里发现端倪。正常线程的日志会显示“RUNNABLE”状态,死锁的线程会显示“BLOCKED”,而且会一直卡在同一个地方。比如你看日志,同一个线程反复在“获取锁”的地方卡住,过了几分钟还是没进展,结合其他线程也在等这个锁,大概率就是死锁了。

模拟复现缩小范围

有时候线上死锁不好直接看,就把问题场景搬到本地模拟。比如你发现程序是在同时处理订单和支付时卡住,就写个小测试,让两个线程分别拿订单锁和支付锁,互相等对方的锁,复现死锁后,就能对应找到线上的代码问题——多半是锁的顺序写反了:你先拿订单锁再拿支付锁,另一个线程先拿支付锁再拿订单锁,自然就形成了循环等待。

排查后的简单优化技巧

找到死锁原因后,不用大改代码,改几个细节就行。比如把锁的顺序统一:所有线程都按“先拿订单锁,再拿支付锁”的顺序,就不会出现互相等待的情况;或者用 tryLock 超时机制,线程拿不到锁就放弃当前的,等会再试,避免一直卡住。

说白了,Java死锁排查没那么神秘,核心是找对互相等待的线程和对应的锁,用工具+逻辑就能快速定位,平时开发多注意锁的顺序,就能减少这类麻烦。

上一篇 Swift语言核心特性与日常开发中的实用技巧科普