死锁

182 阅读4分钟

一、死锁的概念

  死锁是指在一个进程集合中的所有进程都在等待只能由该集合中的其他一个进程才能引发的事件而无限期僵持下去的局面。

二、死锁产生的原因和条件

死锁产生的原因可以总结为下面两点:

  1. 资源竞争(请求同种类型的不可剥夺资源)

      系统资源不足是产生死锁的根本原因。例如,系统中有三个进程A、B、C,其对不可剥夺资源D的需求分别为3、5、2,资源D的总数量为6,当前已分别分配给A、B、C的资源数为1、4、1,此时三个进程都没有拿到足够数量的资源D,但系统中也没有空闲的此类资源,三个进程都要等待其他进程完成后释放资源D才能继续进行,同时又由于自身没有完成而不能推进并最终释放资源,显然此时三个进程互相等待,进入死锁状态。

      当然,系统资源不足时也并不一定就会引起死锁。只要并发进程之间的推进顺序合理,也可能不会引起死锁。

  2. 资源的请求或释放顺序不当(请求多种不同类型的不可剥夺资源)

产生死锁的四个必要条件:

  1. 互斥条件

    在一段时间内,某个资源只能被一个进程使用,如果其他进程提出使用申请,那么后到进程只能等待该资源被当前进程释放后才能使用,即进程对已获取的资源进行排他性使用。即临界资源。

  2. 不剥夺条件

    进程所获得的资源在未使用完毕前,其他进程不能强行剥夺,而只能等待使用该资源的进程完成任务后自行释放。

  3. 请求与保持条件

    已经获取了一部分资源的若又提出新资源申请,且新资源已被别的进程占用时,该进程阻塞自身,并在保持已经获取到的资源的同时等待其他进程释放其他资源。

  4. 环路条件

    在死锁发生时,必然存在一个进程——资源循环等待链,链中每一个进程已获得的资源都同时被下一个进程所请求。

三、死锁的避免

  • 用Java写一个会导致死锁的程序,你将怎么解决?

    /**
     * 使用匿名内部类实现一个死锁
     * @author Hogan_Lee
     * @create 2019-08-31 7:12
     */
    public class DeadLock {
        //定义临界资源
        private static Object resourceA = new Object();
        private static Object resourceB = new Object();
        //定义线程的名称
        private static String ThreadA = "A";
        private static String ThreadB = "B";
    
        public static void main(String[] args) {
            //定义线程A,先后对资源resourceA和resourceB发起请求
            Thread A = new Thread(new Runnable() {
                @Override
                public void run() {
    
    
                    synchronized (resourceA) {
                        System.out.println("线程" + Thread.currentThread().getName() + "获得resourceA");
                        //休眠2秒,确保线程B持有resourceB
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (resourceB) {
                            System.out.println("线程" + Thread.currentThread().getName() + "获得resourceB");
                        }
                    }
                }
            }, ThreadA);
            //定义线程B,先后对资源resourceB和resourceA发起请求
            Thread B = new Thread(new Runnable() {
                @Override
                public void run() {
    
    
                    synchronized (resourceB) {
                        System.out.println("线程" + Thread.currentThread().getName() + "获得resourceB");
                        //休眠2秒,确保线程A持有resourceA
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (resourceA) {
                            System.out.println("线程" + Thread.currentThread().getName() + "获得resourceA");
                        }
                    }
                }
            }, ThreadB);
            A.start();
            B.start();
        }
    }
    
    /**
    
    - 使用Lambda表达式实现一个死锁
    
    - @author Hogan_Lee
    
    - @create 2019-11-27 12:38
    */
    public class Deadlock {
        //临界资源1
        private static Object object1=new Object();
        //临界资源2
        private static Object object2=new Object();
    
        public static void main(String[] args) {
         new Thread(()->{
             synchronized (object1){
                 System.out.println(Thread.currentThread().getName()+"取得object1的锁");
                 try {
                     Thread.sleep(3000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 synchronized (object2){
                     System.out.println(Thread.currentThread().getName()+"取得object2的锁");
                 }
             }
         },"Thread1").start();
         new Thread(()->{
             synchronized (object2){
                 System.out.println(Thread.currentThread().getName()+"取得object2的锁");
                 try {
                     Thread.sleep(3000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 synchronized (object1){
                     System.out.println(Thread.currentThread().getName()+"取得object1的锁");
                 }
             }
         },"Thread2").start();
        }
    }
    

      首先创建了两个资源,并创建了两个线程,线程调度器先调度了线程 A,也就是把 CPU 资源让给了线程 A,线程 A 调用了 getResourceA() 方法,方法里面使用 synchronized(resourceA) 方法获取到了 resourceA 的监视器锁,然后调用 sleep 函数休眠 1s,休眠 1s 是为了保证线程 A 在执行 getResourceB 方法前让线程 B 抢占到 CPU 执行 getResourceB 方法。线程 A 调用了 sleep 后线程 B 会执行 getResourceB 方法里面的 synchronized(resourceB),代表线程 B 获取到了 objectB 对象的监视器锁资源,然后调用 sleep 函数休眠 1s。

      到了这里线程 A 获取到了 objectA 的资源,线程 B 获取到了 objectB 的资源。线程 A 休眠结束后会调用 getResouceB 方法企图获取到 objectB 的资源,而 objectB 资源被线程 B 所持有,所以线程 A 会被阻塞而等待。而同时线程 B 休眠结束后会调用 getResourceA 方法企图获取到 objectA 上的资源,而资源 objectA 已经被线程 A 持有,所以线程 A 和 B 就陷入了相互等待的状态,也就产生了死锁。

      那么如何规避死锁呢?其实造成死锁的原因和申请资源的顺序有很大关系。使用资源申请的有序性原则就可以避免死锁。