教你如何快速排查死锁,如何避免死锁!

前言

相信程序员都会碰上这样的问题,Java死锁如何排查?又如何解决呢?那么,何为死锁呢?死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。今天老顾一次性来帮助大家解决Java死锁的有关问题。

实例

死锁的本质,举个例子如果此时有一个线程 A ,按照先获持有锁 a 再获取锁 b的顺序获得锁,同时另外一个线程 B,按照先获取锁 b 再获取锁 a 的顺序获取锁。如下图所示:
在这里插入图片描述
接着我们用代码模拟上线的执行过程
在这里插入图片描述
直接运行,发现主线程一直处于执行中,一直无法结束
在这里插入图片描述

通过jdk工具jps、jstack排查死锁问题

步骤一:使用jsp查找程序进行

jps:jdk提供的一个工具,可以查看到正在运行的java进程
在这里插入图片描述
步骤二:使用jstack查看线程堆栈信息

jstack:jdk提供的一个工具,可以查看java进程中线程堆栈信息。更详细的用法见文档最后。

$ jstack 96521

在这里插入图片描述
从上面的堆栈信息中我们可以发现这个内容:“Found one Java-level deadlock”,表示程序中发现了一个死锁,后面包含更多详细的信息,重点下面:
在这里插入图片描述
死锁的代码是在DeadLock.java的32行和18行,此时我们就可以去优化代码,解决死锁问题。

通过jdk提供的工具jconsole排查死锁问题

jconsole:jdk提供的一个可视化的工具,方便排查程序的一些问题,如:程序内存溢出、死锁问题等等。更详细的用法见文档最后。jconsole位于jdk的bin目录中

$ jconsole

在这里插入图片描述
可以看到我们的程序,点击连接。
在这里插入图片描述
点击“检测死锁”,可以看到程序死锁信息
在这里插入图片描述
上图中可以看到详细的死锁信息,和jstack中信息类似。

通过jdk提供的工具VisualVM排查死锁问题

VisualVM:jdk提供的一个非常强大的排查java程序问题的一个工具,可以监控程序的性能、查看jvm配置信息、堆快照、线程堆栈信息。算是程序优化的必备工具。工具位于jdk的bin目录中。

$ jvisualvm

在这里插入图片描述
切换到“线程”窗口
在这里插入图片描述
执行提示有死锁情况。在线程窗口中点击“线程Dump”按钮。
查看堆栈信息
在这里插入图片描述
线程堆栈快照的信息和jstack查看到的信息一样,即可发现死锁代码。

如何避免死锁?

我们知道了死锁如何产生的,那么就知道该如何去预防。如果一个线程每次只能获取一个锁,那么就不会出现由于嵌套持有锁顺序导致的死锁

1. 正确的顺序获得锁

如果必须获取多个锁,我们就要考虑不同线程获取锁的顺序。

上面的例子出现死锁的根本原因就是获取所的顺序是乱序的,超乎我们控制的。上面例子最理想的情况就是把业务逻辑抽离出来,把获取锁的代码放在一个公共的方法里面,让这两个线程获取锁都是从我的公共的方法里面获取

当Thread1线程进入公共方法时,获取了A锁,另外Thread2又进来了,但是A锁已经被Thread1线程获取了,所以只能阻塞等待。Thread1接着又获取锁B,Thread2线程就不能再获取不到了锁A,更别说再去获取锁B了,这样就有一定的顺序了。只有当线程1释放了所有锁,线程B才能获取。

比如前面的例子我们改成
在这里插入图片描述
查看打印结果,我们发现 线程0 获取成功然后线程1才能继续获取
在这里插入图片描述

2. 超时放弃

当线程获取锁超时了则放弃,这样就避免了出现死锁获取的情况。当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

总结

死锁就是“两个任务以不合理的顺序互相争夺资源”造成,因此为了规避死锁,应用程序需要妥善处理资源获取的顺序。另外有些时候,死锁并不会马上在应用程序中体现出来,在通常情况下,都是应用在生产环境运行了一段时间后,才开始慢慢显现出来,在实际测试过程中,由于死锁的隐蔽性,很难在测试过程中及时发现死锁的存在,而且在生产环境中,应用出现了死锁,往往都是在应用状况最糟糕的时候——在高负载情况下。因此,开发者在开发过程中要谨慎分析每个系统资源的使用情况,合理规避死锁。

### 排查和解决程序中死锁的方法 #### 什么是死锁死锁是指两个或多个进程在执行过程中因争夺资源而造成的一种僵局状态,其中每个进程都持有某些资源并等待其他进程释放其所需的资源。这种情况下,没有任何一个进程能够继续运行下去。 #### JVM 中的死锁排查方法 为了有效排查 JVM 的死锁问题,可以通过以下方式获取线程转储信息,并分析可能存在的死锁情况: 1. **使用 `jstack` 工具** 可以利用 JDK 自带的 `jstack` 命令来生成线程堆栈信息。该命令会显示当前 Java 进程中所有线程的状态以及它们持有的锁信息。如果存在死锁,则会在输出中标明具体的死锁链路[^1]。 ```bash jstack <pid> ``` 2. **启用 `-XX:+UseDeadLockDetect` 参数** 如果希望 JVM 能够自动检测到死锁,可以在启动应用程序时加入参数 `-XX:+UsePerfData` 和 `-XX:+PrintConcurrentLocks` 来监控并发锁的行为。不过需要注意的是,这种方式可能会带来一定的性能开销。 3. **通过 JConsole 或 VisualVM 图形化界面** 使用这些图形化的管理工具也可以方便地观察线程活动状况及其锁定关系图谱,从而快速定位潜在的死锁风险点。 #### 编码阶段预防措施 为了避免后期诊断带来的麻烦,在编码初期就应该采取相应策略减少发生死锁的可能性: - **保持一致性的加锁顺序** 当多个对象都需要被同步访问的时候,应该遵循固定的加锁次序规则,防止不同路径下交错申请导致循环依赖形成死锁局面[^2]。 - **缩短持有时间** 尽量让临界区内的操作越短越好,这样可以降低其它线程因为长时间等待某个特定资源而陷入阻塞的概率。 - **采用超时机制** 对于那些有可能会长期占用公共资源的操作考虑设置合理的尝试时限,一旦超过指定范围就主动放弃此次请求重新安排后续逻辑处理流程。 #### 数据库层面 (MySQL InnoDB) 处理办法 对于数据库内部产生的事务型死锁现象同样值得关注: - **调整隔离级别** 默认 READ COMMITTED 隔离级相对 SERIALIZABLE 更能规避部分不必要的冲突情形;同时合理设计索引结构有助于提升查询效率进而间接缓解压力条件下的竞争态势[^3]。 - **捕获异常重试业务逻辑** 应用层面上应当具备针对 SQLSTATE '40001' 错误代码做出响应的能力——即当发现由引擎返回此类型错误消息时表示刚刚经历了一次失败提交过程,此时可以选择适当延迟后再重复执行相同指令直至成功完成为止。 ```sql BEGIN; -- Your transactional statements here... COMMIT; EXCEPTION WHEN OTHERS THEN IF SQLCODE = -8006 THEN -- Assuming DBMS-specific error code mapping to deadlock. PERFORM pg_sleep(random() * max_delay); -- Randomized backoff strategy. RAISE NOTICE 'Retrying after encountering a deadlock.'; RETURN retry_logic(); ELSE RAISE; -- Re-throw unexpected exceptions. END IF; END; ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值