#详细介绍!!! 造成死锁的原因以及解决方案!

本篇主要是介绍什么是死锁,已经死锁产生的原因,如果避免死锁。根据上述的几个问题让我们来阅读本篇文章。

目录

1. 什么是死锁

2. 形成死锁的原因(四个必要条件)

3. 如果有效避免死锁



#详细介绍!!! 造成死锁的原因以及解决方案!_第1张图片


 

1. 什么是死锁

死锁主要是锁彼此间进行锁等待,导致每个锁都不能正常执行的情况

例子1:多个锁相互等待造成死锁

假设有两个锁对象为lock1,lock2

线程t1对lock1进行加锁操作,在lock1加锁操作中多lock2再进行加锁操作

线程t2先对lock2进行加锁操作,在lock2加锁操作中又对lock1进行加锁

此时就会造成死锁

伪代码:

public class Demo3 {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t1");
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while(true){
                synchronized (lock2){
                    synchronized (lock1){
                        System.out.println("hello t2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

上述代码则必然会造成死锁

t1 和  t2 线程同时执行

当t1线程对lock1加锁成功了之后,此时lock1已经被占用了,然后t2线程对lock2加锁成功了,此时lock2已经被占用了;

在这种情况下,t1线程在去尝试对lock2进行加锁则会进行锁等待,因为lock2进行被线程t2占用了,那么此时t2线程去尝试对lock1进行加锁也是同理,此时t1占用个lock1不放,t2占用着lock2不放,导致对方都拿不到锁进行等待,那么此时t1和t2线程就不能向下执行死锁了

例子2:同一个线程对同一个锁进行加锁并且该锁是不可重入锁,自己给自己进行锁等待,必然死锁

假设有一个锁lock

代码:

public class Demo4 {
    public static void main(String[] args) {
        Object lock = new Object();
        int a = 0;
        int b = 0;
        synchronized (lock){
            a++;
            synchronized (lock){
                b++;
            }
        }
    }
}

如果是不可重入锁的话上面代码必然死锁

原因,第一次对lock加锁,是此时lock已经被自己占用了,第二次再对lock加锁则发现lock已经被占用此时进行等待lock释放,但是线程已经锁等待了,此时肯定是等不到lock释放的,则造成死锁

注意:由于synchronized是可重入锁,所以上述代码并不会死锁,上面代码只是举个例子说明这种情况

2. 形成死锁的原因(四个必要条件)

本质原因就是,锁之间进行相互等待,等待不到也不会释放手中的资源,相互进行死等导致死锁

而造成这样的底层原因为如下几点:

1. 互斥使用:锁的特性是每次只能有一个线程对锁进行使用,当锁没解锁时,锁资源不会被其他线程正常获取到

2. 不可抢占:资源请求者不能从资源占用者那里抢占到资源,资源只能被占用者主动释放后,请求者才能去获取资源

3. 请求和保持:锁资源请求者在请求其他锁资源时,会保持手中锁资源的占用

如上面例1,t1占用了lock1,再去请求lock2时会仍然占用这lock1,导致其他请求者获取不到

4. 循环等待:线程之间循环占用着彼此的资源,导致彼此间都获取不到资源去正常运行导致死锁

如上面例1:t1占用这个lock1,且正在亲请求lock2,t2占用这lock2,且正在请求lock1;

此时t1占用着t2请求的资源,t2占用着t1请求的资源,这样形成了一个请求闭环,导致相互拿不到资源进而死锁

注意:只要形成了死锁,则必然满足上面四个条件

3. 如果有效避免死锁

既然需要避免死锁,那么就需要根据造成死锁的原因入手。根据上面四条必要条件

1,2点是锁为了保证线程安全锁持有的的特性改变不了

但是3,4则是编码者自己写出来的可以避免

只要破坏了3,4点其中一条自然也就不会死锁

死锁与前面的四个原因是必要关系

方法:破坏循环等待的环路

我们只要保证加锁的顺序一致,那么环路就会得到有效的破坏

前面的例1进行改写:

把两个线程的加锁顺序改为一致

public class Demo3 {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t1");
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while(true){
                synchronized (lock1){
                    synchronized (lock2){
                        System.out.println("hello t2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

此时 t1 和 t2 线程都是先对lock1进行加锁,再对lock2进行加锁,此时就不会产生资源彼此占用的环路了也就不会进行死锁等待

解析:

t1先占用了lock1锁,此时t2就获取不到lock1锁了,那么此时t2就在当前代码位置进行锁等待了,更加执行不到去尝试占用lock2锁的代码了,那么t1线程就能正常拿到lock2的锁资源了,所以此时t1和t2只会执行一个线程,不会形成请求资源的闭环。


本篇文章介绍到这里就差不多了,也欢迎各位铁铁指正文章错误的地方

#详细介绍!!! 造成死锁的原因以及解决方案!_第2张图片#详细介绍!!! 造成死锁的原因以及解决方案!_第3张图片

 #详细介绍!!! 造成死锁的原因以及解决方案!_第4张图片

 

 

你可能感兴趣的:(JavaEE初级,java,开发语言,jvm,java-ee,1024程序员节)