线程死锁

Posted jiatcode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程死锁相关的知识,希望对你有一定的参考价值。

线程死锁

1.死锁

  • 多个线程因竞争资源而造成的一种僵局(互相等待),无外力作用下程序无法推进的情况称之为死锁

  • 如下图:线程P1拥有锁R1,请求锁R2,而线程P2拥有锁R2请求锁R1,彼此都请求不到资源,结束不了方法无法释放对方需要的资源,因此相互等待无法推进,这就是死锁

技术图片

2.产生的四个必要条件

1. 互斥条件

? 进程要求对所分配的资源进行排他性控制,即该资源只能被一个进程占用,其他请求的进程只能等待占用资源的进程结束,释放资源

2.不可剥夺条件

? 进程已经获取了一个资源,在它使用完毕之前,无法被其他进程剥夺走,只能由获取该资源的进程主动释放资源

3.请求与保持条件

? 进程当前已经获取了一个资源,但又提出了一个新的资源请求,而新的资源被占用了,此时请求被阻塞, 当前获取的的资源也无法释放

4.循环等待条件

? 多个进程呈环形互相等待的情况称为循环等待,出现死锁的时候一定是循环等待的情况,但是循环等待不一定就是死锁,但这个闭环中的某个进程请求的资源不仅仅只有一个请求途径,环形外有进程也释放它请求的资源,则可以跑出闭环,则不是死锁

3.死锁演示

以下实现一个死锁:

  • 通过两个线程互相等待的情况来演示

    public class Locks implements Runnable{
        private int flag;   //用来引导两个线程调用不同的资源
        //这里要对两个锁定义static,必须是请求共享资源才会引发死锁
        public static Lock lock = new ReentrantLock();
        public static Lock lock2 = new ReentrantLock();
        public Locks(int flag){
            this.flag = flag;
        }
        public void run() {
            if (flag==1) {
                //请求锁1
                lock.lock();
                try {
                    System.out.println("线程1请求锁2");
                    //持有锁1的时候请求锁2
                    lock2.lock();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                    lock2.unlock();
                }
            }else{
                lock2.lock();
                try {
                    System.out.println("线程2请求锁1");
                    lock.lock();
                    Thread.sleep(1000);
                } finally {
                    lock.unlock();
                    lock2.unlock();
                }
            }
        }
    }
    
    
  • 创建启动线程进行测试:

    public class Demo {
        public static void main(String[] args) {
            Thread thread = new Thread(new Locks(1));
            Thread thread2 = new Thread(new Locks(2));
            thread.start();
            thread2.start();
        }
    }
    
  • 发生死锁

技术图片

4.处理死锁

  • 预防死锁:通过设置限制条件,破坏死锁产生的四个必要条件之一就可以预防
  • 避免死锁:在资源的动态分配过程中,想办法防止系统进入不安全状态(比如两个线程不请求共享的资源)
  • 检测死锁:允许系统发生死锁,但可以设置检测机构即使检测出死锁并采取适当措施解除,该方法操作系统比较常用,他解决了预防死锁和避免死锁会造成的系统处理能力下降,资源利用率降低,保证系统效率的情况下有效处理死锁
  • 接触死锁:检测出死锁后使用某种措施将进程从死锁状态中解脱出来

1.预防

  • 破环请求与保持条件
    • 一次性分配资源,进程获取资源时要获取所有需要的资源,满足条件则分配,不满足则都不分配
    • 要求进程请求新的资源S时要先释放持有的资源R,无论他是不是很快就用到资源R
  • 破坏不可剥夺条件 -- 则资源允许被抢夺
    • 如果进程持有资源的情况下请求新的资源被拒绝,那就要释放当前持有的资源,有需要再重新请求
    • 在两个进程的优先级不一样的情况下,进程一占有进程二请求的资源,操作系统可以抢占进程一的资源,要求他释放,以让进程二请求到资源
  • 破坏循环等待
    • 将系统中的所有资源统一编号,进程在可在任何时候提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。

2.避免

死锁的预防是严格控制产生死锁的必要条件的存在,而避免是不严格控制,因为就算满足了必要条件也不一定会发生死锁,如果这样限制的话,系统的性能将会降低,因此通过一些更优的算法,来避免产生死锁,就算条件满足也不会发生死锁。

1. 有序资源分配法

  • 为所有资源统一编号,所有的线程在请求不同资源时都得按顺序请求

  • 同类资源则要一次请求完毕,即同一台设备的机器打印机传真机要同时申请

  • 举个例子:

    现有两个进程P1,P2,两个资源R1,R2,P1和P2都要请求R1,R2,假设P1先获取到了R1,那么P2就不能先去请求R2,必须等待P1释放R1资源,以此来避免死锁

技术图片

2.银行家算法

银行家算法结构的逻辑如下:
技术图片

分析:

  • 主要涉及几个数值:系统可分配资源(available[j])、进程需要的资源(need[i,j])、进程获取的资源(allocation[i,j])、进程请求的资源(Request[j])
  • 进程开始请求时,会先判断进程请求的资源是不是在进程需要的资源范围内,不是则报错中断请求,然后再进行一个判断,进程请求的资源是否小于或等于系统可分配资源,也就是确认pi请求的资源系统能否分配。
  • 满足以上两个条件后系统会尝试分配资源给进程,计算出分配后的各个数值
  • 再根据算法进行安全性检测,此处的算法先不做说明,等博主学习后会单独写随笔讲讲算法
  • 符合规定就直接分配,不符合的将撤回尝试操作,恢复资源,pi等待

3.顺序加锁

  • 顺序加锁的方法与有序资源法的思路一样,只是它限制的对象时锁Lock,而不是资源
  • 但是该资源只适用于特定场景,因为这种方式需要事先知道所有有可能会用到的锁,但总有些是不法预料到的

4.限时加锁

  • 限时加锁是线程在尝试获取锁的时候加上一个超时的时间,若超过这个时间还获取不到资源的话,就回退并释放已经请求的锁资源,进入等待,随机时间后继续尝试请求
  • 缺点
    • 当线程数量少时,该种方法可以有效避免死锁,但是当线程数量过多时,这些线程的加锁时限大概率是相同的,也是有可能出现一个不断超时重试的死锁
    • java中不能对synchronized同步块设置超时时间,你需要创建一个自定义锁,或使用java5中的java.util.concurrent工具包

3.检测

? 预防和避免死锁系统开销大且不能充分利用资源,更好的方法是不采取任何限制性措施,而是提供检测和解脱死锁的手段,这就是死锁的检测和恢复

死锁检测的数据结构

  • E是现有资源向量(existing resource vector):代码每种资源已经存在的资源总数(一个资源的数组)
  • A是可用资源向量(available resource vector):那么ai表示当前可供使用的资源(一个资源的数组)
  • C是当前分配矩阵(current allocation matrix):该矩阵的行代表一个进行,一个列代表一类资源,即第 i 行表示进程Pi 当前持有的资源总数
  • R是请求矩阵(request matrix):R的第 i 行代表 Pi 所需要所需要的资源

死锁检测步骤

技术图片

  • 如图,算法会寻找一个正在请求资源,且系统可以为其分配的进程,则执行进程,结束后将资源释放添加到系统可分配资源的向量中,直到找不到这样的进程,算法结束,没有被标记的进程就是死锁线程
  • 此处还没结束的进程是指在R矩阵中存在的行对应的进程

4.恢复

抢占

  • 临时将某个资源从所属的进程中,转移到另一个进程
  • 这个做法很需要人工干预,因为在系统中一般是不会主动的释放资源的除非进程结束,做法是否可行取决于资源本身的特性

回滚

  • 周期性的对进程状态进行备份,发现死锁时根据备份恢复进程的状态到未获取所占资源的时刻,将释放的资源分配给其他死锁进程

杀死进程(不推荐)

  • 直接将一个或者若干个进程杀死
  • 尽量保证杀死的进程可以从头再来而不带来任何副作用

以上是关于线程死锁的主要内容,如果未能解决你的问题,请参考以下文章

手撕死锁代码

死锁原理及代码

多线程死锁

怎么处理JAVA多线程死锁问题?

55行代码实现Java线程死锁

死锁的概念以及处理方式