关于DeadLock(死锁)的总结及Java代码实现

面试提问:什么是死锁或者说什么情况会发生死锁?
死锁一般发生在多线程执行的过程中,不同线程争夺相同资源造成了线程间的互相等待。这种等待没有外力作用时会陷入循环等待。
用交通状况做比喻,死锁 = 十字路口交叉相行的车把对方的路给堵死了,造成大堵车情况。

1. 死锁产生条件及其检测算法

死锁的发生具备两个条件:

  • 互斥条件:多个线程不能同时使用同一个资源。如果线程A正持有资源,那么其它线程必须等待,直到线程A释放该资源。
  • 占有式等待条件:即线程B在等待线程A占有的某一资源时,会处于等待状态。并且一直持有着已有的其它资源。
    死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

2. 死锁的解决方式

《并发编程的艺术》中给出的解决方法:

  • 避免一个线程同时获取多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
    以上四种其实是预防方法,如果是已发生死锁,可以有以下几种恢复方式:
  • 利用抢占恢复
  • 利用回滚恢复
  • 通过杀死进程恢复

3. DeadLock的代码演示

为了便于直观理解,本文给出了一个死锁的演示:线程拿到某资源的锁(可以是数据库锁)之后,因为一些异常情况没有释放锁 (死循环),又或者是释放锁的时候抛出了异常,没释放掉。下面一段代码是结合《深入理解Java虚拟机》与极客时间中给出的死锁场景,是实际开发中可能会遇到的具体问题。

public class NormalLock {
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
           //1.1尝试获取原始类型int的Class对象,并在方法中给资源加锁 
            synchronized (Integer.class) {
                System.out.println("t1 Integer");
           //1.2 获取Long的Class对象并给资源加锁
                synchronized (Long.class) {
                    System.out.println("t1 Long");
                }
            }
        });
        threadA.start();//1.3启动线程A
        //2.1获取Long的Class对象并给资源加锁
        Thread threadB = new Thread(() -> {
            synchronized (Long.class) {
                System.out.println("t2 Long");
                synchronized (Integer.class) {
                    System.out.println("t2 Integer");
                }
            }
        });
        threadB.start();//2.2 启动线程B
    }
}

由上述代码中创建了两个资源对象,分别是Int类对象和Long类对象。线程A启动后会先执行代码2.1的Synchronize块试图获取Int类对象这个资源的对象锁,然后执行代码1.2处,尝试获得Long类对象的锁。对于线程B的执行过程亦是如此。
一旦出现死锁,业务是可感知的,因为不能继续提供服务了,那么只能通过dump线程查看 到底是哪个线程出现了问题,以下线程信息告诉我们是DeadLockDemo类的第42行和第31行引起的死锁
方法1: 创建t1和t2两个线程,互相加锁
结果:
在这里插入图片描述
方法2: 给t1一定的线程时间

public class DeadLock {
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            synchronized (Integer.class) {
                System.out.println("t1 Integer");
                try {
                    Thread.sleep(1000);//给线程A休眠1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Long.class) {
                    System.out.println("t1 Long");
                }
            }
        });
        threadA.start();
        Thread threadB = new Thread(() -> {
            synchronized (Long.class) {
                System.out.println("t2 Long");
                synchronized (Integer.class) {
                    System.out.println("t2 Integer");
                }
            }
        });
        threadB.start();
    }
}

结果就是一直卡住:
在这里插入图片描述
可以看出,要避免一个线程同时获取多个锁。

  • 即避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 ·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
    参考文章:
    github—计算机系统之死锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值