如何定位&预防死锁

如何定位&预防死锁

什么是死锁?

简单来说就是并发环境下,两个或两个以上的线程互相等待资源,导致“永久阻塞”的现象

代码示例:

public class Main {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread().getName() + "获取resource1成功");
                try {
                    Thread.sleep(3000);
                    synchronized (resource2) {
                        System.out.println(Thread.currentThread().getName() + "获取resource2成功");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("执行成功");
        }).start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread().getName() + "获取resource2成功");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread().getName() + "获取resource1成功");
                }
            }
            System.out.println("执行成功");
        }).start();

    }
}

运行结果

在这里插入图片描述

代码解读:

  • 线程1先去获取到了resource1这个资源,睡眠了3000ms,可以理解为模拟一个重操作
  • 线程2在线程1睡眠的期间获取到了resource2这个资源,接着去尝试获取resource1资源,但是resource1被线程1所占有,导致了线程2的阻塞等待
  • 线程1在睡眠3000ms后,去尝试获取resource2资源,发现resource2已经被抢占了,于是也阻塞等待资源的释放
  • 最后,线程1和线程2都阻塞住了,也就是发生了“死锁”
怎么去定位死锁?
  1. 通过jps命令查看pid

在这里插入图片描述

  1. 不关闭死锁进程,打开终端输入jstack 死锁进程对应的pid

在这里插入图片描述

在这里插入图片描述

一旦线程发生了死锁,我们在线上一般没有办法进行有效解决,只能去进行程序的重启,不然会导致越来越多的线程堆积,从而出现OOM

所以应对死锁最好的方法就是尽量避免让程序出现死锁????

产生死锁的四大因素:
  • 互斥,共享资源同一时刻只能被一个线程占用
  • 占有并等待:线程在占有资源后,继续尝试占有其他线程正在占有的资源,造成当前线程等待
  • 不可抢占:线程占有的资源只能线程本身释放
  • 循环等待:两个线程互相等待资源的释放
如何避免死锁?

其实破坏死锁就是破坏构造死锁的四大因素之一就可以了

破坏互斥?

互斥性一般是比较难以破坏的,很多资源确实在同一时刻只用一个线程才能使用

破坏占有并等待?

诶,这个还真可以,既然线程存在因为资源获取不到而发生的阻塞等待行为,那么在线程首次执行前就把所有的资源都申请了不就行了,就能保证肯定有一个线程能够正常执行,前提是本身系统资源足以让一个线程执行完成

破坏不可抢占?

这个也可以,如果当前线程申请不到所要的资源,可以手动释放手里的资源,可以使用实现Lock接口的类,例如ReentrantLock的tryLock方法,可以传入锁的超时释放时间,一旦超过传入的超时时间当前线程就会自动释放手中的资源

破坏循环等待条件?

将资源从小到大编号,线程请求资源只能先请求编号较小的资源,线程2不再先去请求resource2,而是先去请求resource1,再去请求resource2;这样就不会导致死锁

总结

通过以上三种方式确实可以破坏死锁,但是会极大的抑制系统的性能

避免死锁算法

银行家算法

银行家算法的实质就是**要设法保证系统动态分配资源后不进入不安全状态,以避免可能产生的死锁。**即没当进程提出资源请求且系统的资源能够满足该请求时,系统将判断满足此次资源请求后系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。

这里就不介绍了,可以看银行家算法

上一篇:深度思考:雪花算法snowflake分布式id生成原理详解


下一篇:缓存技术简介