欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

死锁 以及死锁的解决策略

程序员文章站 2022-04-17 14:38:59
...

死锁

我们需要知道的是,死锁是如何产生的,以及如何相关的应对策略。

1、什么叫死锁?

首先,我们要明确,线程和线程,进程和进程之间都是可以发生死锁的。

进程间的死锁:通俗的来说,就是几个进程由于资源调度不合理,导致两个甚至更多的进程出现了无法向下执行的情况,那么我们就称之为死锁。

线程间的死锁:我们假设有两个线程A和B,线程A持有锁A,线程B持有锁B,此时线程A想要去获取B的锁才能继续向下执行,线程B也想获取A的锁然后向下执行,但是两个线程的锁都在自己手里,别人无法获得,那么两个线程就会陷入一个比较尴尬的境地,无法获取锁也无法向下执行,那么我们就把这种情况叫做线程的死锁。如下图所示:
死锁 以及死锁的解决策略

2、死锁的条件:

我们这里一般使用进程死锁的条件为例:

  • 互斥条件

    就是指某一个资源在某一时刻只能由一个进程获取,其他进程只能等待该进程释放资源以后才能获取。

  • 请求和保持条件

    就是指一个进程获取了自己想要的部分资源,但是想要的其他资源还未分配给自己,此时自己无法往下执行,但是也不会释放已经获取到的资源,那么该进程获取到的资源就相当于进入了阻塞状态。

  • 不可剥夺条件

    在一个资源被进程获取以后,如果进程没有使用完这个资源进行释放,那么其他进程是不能进行抢占使用的。

  • 循环等待条件

    满足上述三个条件以后,就会进入循环等待条件。

3、死锁的应对策略:

3.1 死锁的预防

死锁的预防其实就是打破死锁的条件中至少一个,那么死锁就会不攻自破。

资源的一次性分配,其实就是将系统资源提前进行合理的分配,那么就不会产生那么多的问题了。

资源的有效分配,也就是资源的序号化,其实就是给各个资源一个执行顺序,到了给定的时间就去找自己应该效忠的主人一样,那么自然不会存在资源的抢夺了。

3.2死锁的避免

使用银行家算法。所谓的银行家算法,其实就是提前将系统中各个进程执行需要的资源做一个统计,这个统计会将资源合理的分配给当前资源可以支持完成运行的进程,如果一些进程的资源需求量超过了当前系统资源的剩余量,那么只能等待其他进程将此资源释放以后,再次进行统计,直到系统资源剩余量满足进程执行那么才会分配资源进行执行。

3.3死锁的处理

剥夺资源,甚至直接杀死进程。

4、Java中死锁的实现(摘自大佬的内容)

具体可见https://blog.csdn.net/ZHWang102107/article/details/83042406(感谢感谢!!!)

public static void main(String[] args) {
    final Object a = new Object();
    final Object b = new Object();
    Thread threadA = new Thread(new Runnable() {
        public void run() {
            synchronized (a) {
                try {
                    System.out.println("now i in threadA-locka");
                    Thread.sleep(1000l);
                    synchronized (b) {
                        System.out.println("now i in threadA-lockb");
                    }
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    });
    
    Thread threadB = new Thread(new Runnable() {
    public void run() {
        synchronized (b) {
            try {
                System.out.println("now i in threadB-lockb");
                Thread.sleep(1000l);
                synchronized (a) {
                    System.out.println("now i in threadB-locka");
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }
});

	threadA.start();
	threadB.start();
}

为了解决这个问题,我们不使用显示的去锁,我们用信号量去控制。

信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。

对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。

import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
 
public class UnLockTest {
   public static String obj1 = "obj1";
   public static final Semaphore a1 = new Semaphore(1);
   public static String obj2 = "obj2";
   public static final Semaphore a2 = new Semaphore(1);
 
   public static void main(String[] args) {
      LockAa la = new LockAa();
      new Thread(la).start();
      LockBb lb = new LockBb();
      new Thread(lb).start();
   }
}
class LockAa implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 开始执行");
         while (true) {
            if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockA 锁住 obj1");
               if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockA 锁住 obj2");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockA 锁 obj2 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockA 锁 obj1 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockBb implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 开始执行");
         while (true) {
            if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockB 锁住 obj2");
               if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockB 锁住 obj1");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockB 锁 obj1 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockB 锁 obj2 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

如何避免死锁

1、我们可以使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间

2、信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。

对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出

相关标签: JAVA基础知识