zoukankan      html  css  js  c++  java
  • 线程死锁

    线程死锁

    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.恢复

    抢占

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

    回滚

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

    杀死进程(不推荐)

    • 直接将一个或者若干个进程杀死
    • 尽量保证杀死的进程可以从头再来而不带来任何副作用
  • 相关阅读:
    基于FFI模块CAPI与JavaScript的各种类型匹配总结
    在Electron中通过ffi模块实现JavaScript调用C++动态库
    谷歌地图OGC WMTS服务规则
    tiff/tfw, jpg/jpgw坐标文件的格式(6个参数)
    GreenDao 多表事务操作
    Asp.net WebAPI 使用流下载文件注意事项
    mvn 用指定setting.xml 执行指定pom.xml
    Swagger自动生成接口文档
    Windows下控制Nginx的状态
    Android 动态权限申请
  • 原文地址:https://www.cnblogs.com/JIATCODE/p/13276343.html
Copyright © 2011-2022 走看看