多线程学习---死锁

线程死锁

 死锁是指两个或两个以上的线程在执行过程中,因为争夺资源而造成的互相等待的现象。在无外力作用下,这些线程会一直互相等待而无法继续运行下去

  死锁产生的条件

  1、互斥条件:指线程对已经获取到的资源进行排他性使用。即该资源同时只能由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直到占有资源的线程释放该资源

  2、请求并持有条件:指一个线程已经持有了至少一个资源,但又提出新的资源请求,而新的资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源

  3、不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕之后才由自己释放该资源

  4、环路等待条件:指在发生死锁的时候,必然存在一个(线程---资源)的环形链,即线程集合{T0,T1,T2。。。Tn}中的T0正在等待一个T1占用的资源,T1在等待T2占用的资源,。。。。Tn在等待T0占用的资源

  例如:

public static void main(String[] args) throws Exception{
        Object obj1=new Object();
        Object obj2=new Object();
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                //获取obj1的监视器锁
               synchronized (obj1){
                   System.out.println("线程A运行");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("线程A中睡眠完成");
                   //获取obj2的监视器锁
                   synchronized (obj2){
                       System.out.println("线程A中获取obj2的资源");
                   }
               }
            }
        });
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj2){
                    System.out.println("线程B运行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程B睡眠完成");
                    synchronized (obj1){
                        System.out.println("线程B中获取obj1的资源");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();
    }

  执行结果:

  

 

   代码中,分别创建了两个子线程A和子线程B,通过运行结果可看到,线程调度器先调用了子线程A,线程A通过同步代码块获取到了obj1的监视器锁,然后开始休眠1秒,然后线程调度器调用子线程B,子线程B通过同步代码块获取到了obj2的监视器锁,然后子线程B休眠1秒,到这里,线程A获取到了obj1的资源,线程B获取到了obj2的资源,然后线程A休眠完成后要获取obj2的资源,而线程B休眠完成后要获取obj1的资源,而obj1被A持有,没有释放,obj2被B持有没有释放,这个时候,A等B释放obj2的资源,B在等A释放obj1的资源,相互等,然后就陷入死锁状态;

  这段代码中是如何满足死锁的四个条件的:

    首先,obj1和obj2都是互斥资源,当线程A调用synchronized(obj1)方法获取到obj1上的监视器锁并释放前,线程B在调用synchronized(obj1)方法尝试获取该资源的时候就会被阻塞,只有线程A主动释放了该锁,线程B才能获得,这满足资源互斥条件

    线程A首先通过synchronized(obj1)获取到obj1的监视器锁资源,然后通过synchronized(obj2)等待获取obj2上的监视器锁资源,这就构成了请求并持有条件

    线程A在获取obj1上的监视器锁资源后,该资源不会被线程B掠夺走,只有线程A自己主动释放obj1的资源时,他才会放弃对该资源的持有权,这就构成了资源不可剥夺条件

    线程A持有obj1的资源并等待获取obj2的资源,而线程B持有obj2的资源等待获取obj1的资源,这就构成了环路等待条件,所以线程A和线程B就进入了死锁状态

 

如何避免死锁

 要想避免死锁,只需要破坏掉至少一个构成死锁的必要条件即可。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。例如将上边的代码改成线程A和线程B获取资源的顺序一致

 

public static void main(String[] args) throws Exception{
        Object obj1=new Object();
        Object obj2=new Object();
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                //获取obj1的监视器锁
               synchronized (obj1){
                   System.out.println("线程A运行");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("线程A中睡眠完成");
                   //获取obj2的监视器锁
                   synchronized (obj2){
                       System.out.println("线程A中获取obj2的资源");
                   }
               }
            }
        });
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj1){
                    System.out.println("线程B运行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程B睡眠完成");
                    synchronized (obj2){
                        System.out.println("线程B中获取obj2的资源");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();
    }

  执行结果:

  

 

   修改完线程B的获取资源顺序,就避免了死锁的产生,以上代码中,假若线程A和线程B同时执行到了synchronized (obj1)那么就有一个获取到了obj1的监视器锁资源,例如线程A获得,而线程B就会进入阻塞状态不会获取obj2的资源,而等到获取到资源的线程A睡眠完成去获取obj2的资源,这时obj2的资源并没有被其他线程占用,线程A就可以直接获取obj2的监视器锁资源,直到线程A执行完毕,释放资源,线程B才会获取到obj1的监视器锁继续执行,等到线程B获取obj2的资源时,线程A早已将资源释放,自然线程B也就可以获取到obj2的监视器锁资源了,也就没有了死锁的条件了

posted @ 2021-04-11 21:01  莫慌*急啥  阅读(67)  评论(0)    收藏  举报