死锁的原因和解决方案

目录

死锁

死锁的情况

构成死锁的原因

解决方案

死锁

死锁就是同时有多个线程被阻塞,他们中的一个或者全部都在等待某个资源的释放,这样就导致线程被无限期阻塞,整个进程也不会结束。

接下来我们看个例子:

 可以看出线程A和线程B都同时持有不同的资源,此时就不会造成阻塞,也不会行程死锁。

如果说此时线程A想要线程B的资源,线程B想要线程A的资源,就会造成下列情况。

此时线程A如果想要线程B的资源,那么就需要等待线程B进行资源的释放,同理,线程B想要线程A的资源,也要等待线程A进行资源的释放。

线程A在尝试获取资源1的时候,此时资源1是被线程B 持有的,线程1就会进行阻塞,线程B在尝试获取资源2的时候,此时资源2是被线程A持有的,所有线程B也会进行阻塞等待。

这个情况就是线程A阻塞等待线程B进行资源释放,但是线程B也在等待线程A进行资源释放,此时就造成了死锁。

我们可以举个简单的例子:

有个老铁在和他的女朋友吃饺子的时候,比如饺子的蘸酱有酱油和醋,此时老铁拿起来醋,老铁女朋友拿起了酱油,此时他们同时有想要对方的蘸酱,老铁给他女朋友说,你把酱油给我,等我把醋和酱油都弄好之后,我在给你,老铁的女朋友也说,你先把醋给我,等我把醋和酱油都弄好之后,我再给你。由于老铁的脾气很犟,于是老铁也就不管了,说那就我们这样等着吧,此时老铁女朋友也脾气犟起来了,说那好,那就我们一直这样等着吧。

于是上述的情况我们可以理解为死锁了。

接下来我们通过代码再进行理解:

public class test {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (object1) {
                System.out.println("hello1");
                synchronized (object2) {
                    System.out.println("hello2");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (object2) {
                System.out.println("hello2");
                synchronized (object1) {
                    System.out.println("hello1");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

在上述代码中我们可以看到,在t1线程和t2线程成功启动之后,于是两个线程开始并发的执行各自的任务,由于线程之间是抢占式执行,执行顺序是随机的。

假如我们此时先看t1线程,在对object1对象加锁之后,然后输出了hello1,此时t2线程也开始执行了,再对object2加锁之后,输出了hello2,此时CPU切换到t1线程上,t1线程要对object2对象进行加速,但是object2是被t2线程已经加锁过,此时这个锁对象还没有被释放,于是t1线程阻塞等待,此时CPU切换到t2线程上,t2线程再对object1对象进行加锁,但是object1是被t1线程加锁中的,所以t2线程也阻塞等待。

此时我们看出,t1线程要想成功加锁object2对象,就得等待t2线程释放object2对象,但是t2线程此时阻塞在等待t1线程释放object1对象,那么t1线程要想释放object1对象,就得先把object2对象加锁,但是t2在持有object2的锁对象的同时在进行阻塞,t1线程也在持有object1的锁对像的同时也在进行阻塞。

这就是一个典型的死锁现象。

接下来我们看看关于死锁的情况。

死锁的情况

1:一个线程一把锁,此时是不会构成死锁的。

因为synchronized是可重入锁,所以即便是我们针对某一个对象连续加锁,也不会构成死锁。

代码实现:

public class test {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t = new Thread(()->{
           synchronized (object) {
               synchronized (object) {
                   System.out.println("hello");
               }
           }
        });
        t.start();
    }
}

因为synchronized在加锁的时候会判定一下,当前申请锁的线程是不是锁的持有者,如果是,那么就直接放行。

2:两个线程两把锁,就会构成死锁

public class test {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (object1) {
                System.out.println("hello1");
                synchronized (object2) {
                    System.out.println("hello2");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (object2) {
                System.out.println("hello2");
                synchronized (object1) {
                    System.out.println("hello1");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

这个代码就是两个线程两把锁,构成了死锁。

3:N个线程,M把锁,线程数和锁的数量更多了,也就根容易构成死锁了。

一个典型的问题,就是哲学家就餐问题:

 此时每个哲学家的左右两边都有两根筷子,这个5个哲学家同时的进行吃面条操作和放下筷子的操作。

那么此时比如有个哲学家想拿起筷子进行吃面条操作,但是发现左边或者右边的筷子被另一个哲学家已经拿走了,此时这个哲学家很固执,他也不会放下另一个筷子,而是进行等待。这个哲学家就会阻塞。

如果这5个哲学家同时拿起来左边的筷子,就会发现死锁了!!!

那么什么原因构成的死锁呢 ?  我们接着往下看。

构成死锁的原因

死锁的原因主要有4个:

1:互斥使用    一个线程拿到一把锁之后,另一个线程不能使用(锁的基本特点)

2:不可抢占    一个线程拿到锁,只能自己主动释放,不能被其他线程强行占有。

3:请求和保持   一个线程因为加锁被阻塞时,同时也不会释放自己已经加过的锁。

4:循环等待     上述两个线程两把锁的情况就是循环等待, 可以简单理解为车钥匙锁家里面,家里要是锁车里面了。

上述的4个原因缺一不可,也就是说要想构成死锁,就得同时满足上述4个条件。

解决方案

解决死锁问题的方案其实就是破解上述4的条件的其中任意一个。

其中最容易破坏的就是循环等待。

如何破解循环等待:

我们可以针对锁对象进行锁排序,假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号
(1, 2, 3...M).
N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免循环等待。

破解循环等待:

上述的两个线程两把锁的代码是死锁代码,那么接下来我们破解循环等待来破解死锁。

public class test {
    public static void main(String[] args) throws InterruptedException {
        Object object1 = new Object();
        Object object2 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (object1) {
                System.out.println("hello1");
                synchronized (object2) {
                    System.out.println("hello2");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (object1) {
                System.out.println("hello1");
                synchronized (object2) {
                    System.out.println("hello2");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

上述代码我们可以发现,每个线程在加锁的时候,因为有多个锁对象,我们给这个多个锁对象从小到大进行排序。此时我们就很好的破解了循环等待,也就解决了死锁的问题。

猜你喜欢

转载自blog.csdn.net/qq_63525426/article/details/130029317