当前位置: 首页 > article >正文

什么是死锁?怎么预防?如何解决?

在并发编程中,死锁(Deadlock) 是一个常见的问题,尤其是在涉及多线程时。死锁指的是两个或多个线程互相等待对方释放资源,从而进入无限等待的状态,导致整个系统无法继续执行。理解死锁的成因及预防机制,对于编写高效、安全的多线程程序至关重要。

一. 什么是死锁?

死锁是指两个或多个线程互相等待对方持有的资源而永远无法继续执行的状态。在 Java 中,死锁通常发生在使用 synchronized 关键字或者其他锁机制进行同步操作时。如下图,两个线程在保持自己持有的资源的同时永久等待另一个线程持有的资源,就会导致死锁现象的发生。

二. 死锁的必要条件

死锁的产生通常依赖于以下四个条件,这四个条件同时满足时,就有可能发生死锁:

  1. 互斥条件:线程对所需的资源具有独占访问权,也就是说某个资源在同一时刻只能被一个线程占用。
  2. 请求和保持:线程已经持有了一个资源,并且还在等待获取另外一个资源。
  3. 不可剥夺:资源不能被强制从一个线程中剥夺,线程必须自愿释放它占用的资源。
  4. 循环等待:存在一个线程循环等待链,链中的每个线程都等待下一个线程持有的资源。

只要四个条件同时存在,就有可能导致死锁。

三. Java 中死锁的代码示例

让我们通过一个简单的demo展示 Java 中可能发生死锁的情况。

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void method1() {
        synchronized (lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock2) {
                System.out.println("Thread 1: Holding lock 1 & 2...");
            }
        }
    }

    public void method2() {
        synchronized (lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock1) {
                System.out.println("Thread 2: Holding lock 2 & 1...");
            }
        }
    }

    public static void main(String[] args) {
        DeadlockExample deadlock = new DeadlockExample();
        Thread thread1 = new Thread(deadlock::method1);
        Thread thread2 = new Thread(deadlock::method2);

        thread1.start();
        thread2.start();
    }
}

        可以看出,thread1 先获取 lock1,然后试图获取 lock2thread2 先获取 lock2,然后试图获取 lock1。由于 thread1thread2 分别在等待对方释放资源,从而发生了死锁。

四、java中如何检测死锁

在 JVM 中,通过使用 jstack 命令可以查看线程堆栈信息,从而发现死锁的情况。

执行以下命令可以获取 Java 进程的线程堆栈:

jstack <pid>

jstack 输出中会明确指出是否有死锁存在,并列出哪些线程处于死锁状态。

五、如何预防死锁

上面第二节说了死锁的四个必要条件,只有同时满足这四个条件时才会发生死锁,那么我们在预防死锁时只需要破坏其中的一个条件即可,下面我们对四个必要条件的破坏进行逐个分析:

  1. 破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

  2. 破坏请求与保持条件 :一次性申请所有的资源。

  3. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

  4. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

以上是预防死锁的理论基础,那么实际生产环境中我们怎么预防死锁呢?

1. 避免嵌套锁

嵌套锁是导致死锁的主要原因之一。尽量避免一个线程同时持有多个锁,可以通过重构代码来减少对多个资源的锁定。例如,将大部分逻辑放在持有单个锁的代码块中。

2. 固定锁的顺序

如果线程必须持有多个锁,确保所有线程以相同的顺序请求锁。通过这种方式,可以避免循环等待情况的发生。例如,如果所有线程总是先请求 lock1,然后再请求 lock2,就可以防止死锁。

3. 使用 tryLock()

Java 的 ReentrantLock 提供了 tryLock() 方法,它尝试获取锁,如果获取失败,它不会阻塞线程,而是返回 false。这样可以避免线程陷入死锁。

4. 锁超时机制

如果使用 ReentrantLock,可以在 tryLock() 中设置超时参数。如果线程在指定时间内未能获得锁,便自动放弃锁请求。这有助于避免长时间的锁等待而导致的死锁。

5. 避免过多锁

尽量减少对锁的依赖,使用无锁算法、原子变量(如 AtomicInteger)或者并发集合(如 ConcurrentHashMap),这些数据结构能够在高并发的情况下提供线程安全而无需显式的锁机制。

六、如何解决死锁

在检测到死锁之后,怎么解决已经发生的死锁呢?

修改代码!

大多数死锁的产生都是因为代码有问题,我们需要找出哪些线程和哪些资源导致了死锁,进而找到问题代码。下面介绍了一些优化代码的方法:

1、固定锁的获取顺序

2、使用 tryLock() 代替 synchronized

3、使用超时策略

4、精细锁的粒度

实际上还是终止程序来预防死锁。


http://www.kler.cn/a/301144.html

相关文章:

  • 怎么防止SQL注入攻击
  • 【ROS2】数据记录(ros2 bag)详解
  • 穷举vs暴搜vs深搜vs回溯vs剪枝系列一>优美的排列
  • (12)springMVC文件的上传
  • uniApp通过xgplayer(西瓜播放器)接入视频实时监控
  • 宁德时代C++后端开发面试题及参考答案
  • 如何在群晖NAS中安装HA平台并实现异地控制智能家居设备实战教程
  • 90、k8s之secret+configMap
  • async和await真题
  • h5页面使用antd-modal,怎么处理居中且自然
  • Flutter-底部选择弹窗(showModalBottomSheet)
  • Ubuntu系统使用Docker部署Jupyter Notebook并实现笔记云同步
  • Java 面试题:Java的垃圾收集算法 --xunznux
  • 算法岗/开发岗 实况
  • 不允许有程序员不知道这款AI代码扩写工具
  • 学会分析问题,画出分析图,解释问题过程,找出规律 ;整数数组分为左右2个部分,左边位奇数右边偶数
  • B端产品经理的流程设计思维
  • Selenium面试题(二)
  • 从自动化到智能化的研究
  • Vue邮件发送:如何有效集成邮件发送功能?
  • day20JS-axios数据通信
  • Java面试题·解释题·框架部分
  • FastGPT自定义插件的icon
  • Ubuntu系统Docker部署数据库管理工具DbGate并实现远程查询数据
  • Hash算法与Hash冲突
  • JS_分支结构