千家信息网

如何检测并避免 Java 中的死锁

发表于:2024-09-22 作者:千家信息网编辑
千家信息网最后更新 2024年09月22日,今天就跟大家聊聊有关如何检测并避免 Java 中的死锁,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。如果你没有参与过多线程并发 Java 应用
千家信息网最后更新 2024年09月22日如何检测并避免 Java 中的死锁

今天就跟大家聊聊有关如何检测并避免 Java 中的死锁,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

如果你没有参与过多线程并发 Java 应用程序的编码,你可能会失败。

# 如何避免 Java 线程死锁?


这是 Java 面试 的热门问题之一, 也是多线程的编程中的重口味之一, 主要在招高级程序员时容易被问到, 且有很多后续问题。


尽管问题看起来非常基本, 但大多数 Java 开发人员一旦你开始深入, 就会陷入困境。


"什么是死锁?????"


当两个或多个线程在等待彼此释放所需的资源(锁定)并陷入无限等待即是死锁。它仅在多任务或多线程的情况下发生。


# 如何检测 Java 中的死锁?


虽然这可以有很多答案, 但我的版本是首先我会看看代码, 如果我看到一个嵌套的同步块,或从一个同步的方法调用其他同步方法, 或试图在不同的对象上获取锁, 如果开发人员不是非常小心,就很容易造成死锁。


另一种方法是在运行应用程序时实际锁定时找到它, 尝试采取线程转储,在 Linux 中,你可以通过kill -3命令执行此操作, 关注公众号小黄鸭编程社区,回复关键字手册,获取最新开发手册,这将打印应用程序日志文件中所有线程的状态, 并且你可以看到哪个线程被锁定在哪个线程对象上。


你可以使用 fastthread.io 网站等工具分析该线程转储, 这些工具允许你上载线程转储并对其进行分析。


另一种方法是使用 jConsole 或 VisualVM, 它将显示哪些线程被锁定以及哪些对象被锁定。


如果你有兴趣了解故障排除工具和分析线程转储的过程, 我建议你看看 Uriah Levy 在多元视觉(PluraIsight)上《分析 Java 线程转储》课程。旨在详细了解 Java 线程转储, 并熟悉其他流行的高级故障排除工具。


# 编写一个将导致死锁的Java程序?


一旦你回答了前面的问题,他们可能会要求你编写代码,这将导致Java死锁。

/**   * Java 程序通过强制循环等待来创建死锁。 */  public class DeadLockDemo {
/* * 此方法请求两个锁,第一个字符串,然后整数 */ public void method1() { synchronized (String.class) { System.out.println("Aquired lock on String.class object");
synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); } } }

/* * 此方法也请求相同的两个锁,但完全 * 相反的顺序,即首先整数,然后字符串。 * 如果一个线程持有字符串锁,则这会产生潜在的死锁 * 和其他持有整数锁,他们等待对方,永远。 */ public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } }

如果 method1() 和 method2() 都由两个或多个线程调用,则存在死锁的可能性, 因为如果线程 1 在执行 method1() 时在 Sting 对象上获取锁, 线程 2 在执行 method2() 时在 Integer 对象上获取锁, 等待彼此释放 Integer 和 String 上的锁以继续进行一步, 但这永远不会发生。


此图精确演示了我们的程序, 其中一个线程在一个对象上持有锁, 并等待其他线程持有的其他对象锁。


你可以看到, Thread1 需要 Thread2 持有的 Object2 上的锁,而 Thread2 希望获得 Thread1 持有的 Object1 上的锁。由于没有线程愿意放弃, 因此存在死锁, Java 程序被卡住。


其理念是, 你应该知道使用常见并发模式的正确方法, 如果你不熟悉这些模式,那么 Jose Paumard 《应用于并发和多线程的常见 Java 模式》是学习的好起点


# 如何避免Java中的死锁?


现在面试官来到最后一部分, 在我看来, 最重要的部分之一; 如何修复代码中的死锁?或如何避免Java中的死锁?


如果你仔细查看了上面的代码,那么你可能已经发现死锁的真正原因不是多个线程, 而是它们请求锁的方式, 如果你提供有序访问, 则问题将得到解决。


下面是我的修复版本,它通过避免循环等待,而避免死锁, 而不需要抢占, 这是需要死锁的四个条件之一。

public class DeadLockFixed {
/** * 两种方法现在都以相同的顺序请求锁,首先采用整数,然后是 String。 * 你也可以做反向,例如,第一个字符串,然后整数, * 只要两种方法都请求锁定,两者都能解决问题 * 顺序一致。 */ public void method1() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } }
public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } }

现在没有任何死锁,因为两种方法都按相同的顺序访问 Integer 和 String 类文本上的锁。

因此,如果线程 A 在 Integer 对象上获取锁, 则线程 B 不会继续, 直到线程 A 释放 Integer 锁, 即使线程 B 持有 String 锁, 线程 A 也不会被阻止, 因为现在线程 B 不会期望线程 A 释放 Integer 锁以继续。

看完上述内容,你们对如何检测并避免 Java 中的死锁有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

0