幽灵般的死锁:它是如何诞生的,又该如何斩杀?

发布时间:2026/7/9 21:58:08

幽灵般的死锁:它是如何诞生的,又该如何斩杀? 幽灵般的死锁它是如何诞生的又该如何斩杀前言一场“你不松手我也不放”的生死僵局我们在写多线程代码时加锁是为了保证数据的绝对安全。但是如果锁用得不好就会引来多线程开发中最可怕的幽灵——死锁Deadlock。一旦发生死锁程序就彻底卡死CPU不消耗也没有任何报错日志用户的请求一直在转圈圈直到系统崩溃。今天我们就来彻底搞懂死锁是怎么产生的并在生产环境中如何像“抓鬼”一样把它揪出来一、 小白科普到底什么是死锁我们用一个非常接地气的例子来解释假设有两个小朋友小明和小红在打架。小明死死抓住了小红的头发。小红死死拽住了小明的衣服。此时小明喊话“你先松开我的衣服我才松开你的头发”小红也不甘示弱“不行你先松开我的头发我才松开你的衣服”结果这俩人互不相让谁也不肯先放手就这么一直僵持下去直到永远……这就是死锁在计算机中“抓头发”和“拽衣服”就是获取锁对象的操作。二、 召唤死锁的“四大阵法”死锁产生的四大必要条件面试必问死锁不是你想有就能有的必须同时满足以下四个条件才会发生互斥条件资源只有一把锁比如一份衣服只能被一个人拽着。请求和保持条件小明已经抓住了头发保持还试图去抓衣服请求并且在没抓到衣服前打死也不放开头发。不剥夺条件小明抓着头发小红不能强行把小明的手掰开不能被外界强行剥夺资源。循环等待条件小明等小红小红等小明形成了一个闭环。三、 代码复现写一段 100% 触发死锁的代码让我们用 Java 代码把小明和小红复现出来publicclassDeadLockDemo{// 定义两把锁两个资源privatestaticfinalObjectlockAnewObject();// 衣服privatestaticfinalObjectlockBnewObject();// 头发publicstaticvoidmain(String[]args){// 线程 1代表小明Threadt1newThread(()-{synchronized(lockA){System.out.println(小明抓住了【衣服】...);try{// 睡 1 秒确保小红也能抓住头发Thread.sleep(1000);}catch(InterruptedExceptione){}System.out.println(小明准备抓【头发】...);synchronized(lockB){// 【危险】小明带着 lockA去请求 lockBSystem.out.println(小明全部抓住了);}}},小明);// 线程 2代表小红Threadt2newThread(()-{synchronized(lockB){System.out.println(小红抓住了【头发】...);try{// 睡 1 秒确保小明也能抓住衣服Thread.sleep(1000);}catch(InterruptedExceptione){}System.out.println(小红准备抓【衣服】...);synchronized(lockA){// 【危险】小红带着 lockB去请求 lockASystem.out.println(小红全部抓住了);}}},小红);t1.start();t2.start();}}运行结果控制台打印完准备抓对方的资源后程序永远卡死不再往下执行也不会自动退出。四、 斩杀幽灵生产环境如何排查死锁这是面试中高频的实战题。在线上环境服务器突然失去响应你是怎么排查出死锁的工具三板斧jps jstack第一步找进程。打开终端输入jps找到你的 Java 项目运行的进程号PID。例如找到 PID 为12345。第二步打快照。输入命令jstack 12345这个命令会把当前这台 JVM 里所有线程的状态打印出来。第三步寻找“罪证”。往下滑动 jstack 的输出日志如果你看到了类似下面的这段话稳了罪魁祸首就是死锁Found one Java-level deadlock: 小明: waiting to lock monitor 0x00... (object 0x00..., a java.lang.Object), which is held by 小红 小红: waiting to lock monitor 0x00... (object 0x00..., a java.lang.Object), which is held by 小明 Java stack information for the threads listed above: // 这里会精准地指出哪一个类的哪一行代码发生了死锁五、 如何预防死锁怎么解决它知道了死锁的四大必要条件预防死锁的套路就是打破这些条件打破“请求与保持”一次性申请所有的资源如果申请不到就一个也不拿。打破“不剥夺”设置一个超时时间。比如使用ReentrantLock.tryLock(3, TimeUnit.SECONDS)。如果等了 3 秒还是拿不到下一把锁就主动放弃并把之前的锁也释放掉退一步海阔天空。打破“循环等待”最常用最有效按固定顺序加锁规定所有人必须先抓衣服再抓头发。比如上述代码中如果把小红的加锁顺序也改成synchronized(lockA)里面嵌套synchronized(lockB)死锁就会灰飞烟灭程序将乖乖排队执行。总结死锁并不可怕可怕的是遇到死锁时不知所措。牢记这四大必要条件在日后的并发编程中尽量避免嵌套加锁如果不可避免一定要保证所有线程加锁的顺序是一样的实在不行就用ReentrantLock加上超时机制兜底保命

相关新闻