多线程中死锁是如何产生的?如何检测?如何避免?

一、死锁是如何产生的?

死锁:是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。具体来说,每个线程持有一部分资源,并等待其他线程所持有的资源释放,导致所有线程都无法继续执行。

例如:

线程A 获得 lockX 对象锁,接下来想获取 lockY对象的锁;

线程B 获得 lockY 对象锁,接下来想获取 lockX对象的锁。

代码如下:

public class DeadlockTest {

    private static Object lockX = new Object();
    private static Object lockY = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (lockX) {
                System.out.println("Thread A: Holding lock X...");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("Thread A: Waiting for lock Y...");
                synchronized (lockY) {
                    System.out.println("Thread A: Holding lock X and lock Y...");
                }
            }
        }, "threadA"); // 线程起别名

        Thread threadB = new Thread(() -> {
            synchronized (lockY) {
                System.out.println("Thread B: Holding lock Y...");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("Thread B: Waiting for lock X...");
                synchronized (lockX) {
                    System.out.println("Thread B: Holding lock Y and lock X...");
                }
            }
        }, "threadB");

        threadA.start();
        threadB.start();
    }

}

控制台输出结果:

此时程序并没有结束,这种现象就是死锁现象...线程A持有lock X锁等待获取lock Y锁,线程B持有lock Y锁等待获取lock X锁。  


二、如何进行死锁诊断?

当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

步骤如下:

第一:查看运行的线程

打开终端,运行命令:jps,得到Java进程的PID、主类的完整名称。

第二:使用jstack查看线程运行的情况 

运行命令:jstack -l 18348

发现一个死锁: 


三、如何避免死锁?

预防死锁

预防死锁的策略是破坏死锁的四个必要条件之一:

  1. 互斥条件:确保资源可以被同时访问,不过并非所有资源都能够做到这一点。
  2. 请求与保持条件:一次性申请所有资源,不允许分步申请。
  3. 不可剥夺条件:一旦资源被分配给某线程,就不应该从该线程中强制夺回。
  4. 循环等待条件:对资源实施一个全局的顺序,确保所有线程按顺序申请资源。

避免死锁

避免死锁的策略是在运行时避免系统进入不安全状态。银行家算法是一个著名的避免死锁的算法,它通过动态分析资源分配来确保系统始终处于安全状态。


四、解决死锁示例:

在上面的例子中,如果线程A和线程B几乎同时开始执行,它们可能会相互等待对方释放锁,从而导致死锁。

解决这个问题的一种方法是确保所有线程按相同的顺序获取锁:

public class DeadlockResolvedDemo {

    private static Object lockX = new Object();
    private static Object lockY = new Object();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (lockX) {
                System.out.println("Thread A: Holding lock X...");

                try { Thread.sleep(100); } catch (InterruptedException e) {}

                System.out.println("Thread A: Waiting for lock Y...");
                synchronized (lockY) {
                    System.out.println("Thread A: Holding lock X and lock Y...");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (lockX) { // 注意这里也是先获取lockX
                System.out.println("Thread B: Holding lock X...");

                try { Thread.sleep(100); } catch (InterruptedException e) {}

                System.out.println("Thread B: Waiting for lock Y...");
                synchronized (lockY) {
                    System.out.println("Thread B: Holding lock X and lock Y...");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

在这个修改后的例子中,无论线程A还是线程B,都是先尝试获取lockX,然后再获取lockY,这样就打破了循环等待条件,从而避免了死锁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值