Java多线程结合银行家算法避免死锁实践

​ 在前几篇文章中,我们讨论了银行家算法,包含其数据结构、算法步骤和安全性算法。关于银行家算法的具体细节,请参看这篇博文

​ 在另一篇文章中,我们使用了Java来模拟实现了银行家算法,并使用一个例子来验证了算法的有效性,具体实现请参看这篇博文

​ 在Java实现的这篇文章最后,博主提到了,可考虑使用Java多线程来模拟OS中的进程死锁问题,并使用银行家算法来避免死锁,其实我自己也有些心痒痒的,毕竟多线程编程在上次写线程同步的几个经典问题后就没怎么碰过了,手还是很痒痒呢。

资源分配图


​ 废话不多说了,代码直接撸起来。

​ 首先,我们要设定场景,怎么样才可以让线程非常容易的就可以发生死锁,并且资源种类、数量还没这么复杂?这里我们选定在某篇文章中一个例题,题目如下:

​ 例题:假定系统中有三个进程P1、P2和P3,共有12台磁带机。进程P1总共要求10台磁带机,P2和P3分别要求4台和9台。假设在T0时刻,进程P1、P2和P3已分别获得5台、2台和2台磁带机,尚有3台空闲未分配,如下表所示:

资源分配图

​ 在这题目中,为了让进程更简单,也让发生死锁的概率更大,我们对进程增加个限制,每个线程需要申请两次或三次资源,第一次申请已分配这栏的资源,第二次申请2个资源,如有第三次则申请需要的剩余所有资源(最大需求减已分配减二),全部申请到资源后,释放全部资源,睡眠一段时间后,再次运行。

​ 注:下文中可能部分进程和线程的概念会交织在一起,涉及到题目相关的,都是线程的概念,因为我们是使用Java多线程来模拟多进程的并发;涉及到进程并发的问题,那就是OS中的进程概念。😵

1.进程的并发问题

​ 关于进程并发的问题,有兴趣的同学可以去参看这篇文章,这里就不赘述了。

​ 在这里,为了保证进程在通过银行家算法请求资源时的同步问题,我们这里采用管程来解决,通过管程来封装获取资源和释放资源的方法,保证各个线程间的同步问题。使用信号量来代替磁带机;为了省去申请多次资源的问题,这里使用信号量集的机制。

​ 关于管程部分,可以参考这篇博客:Java并发编程模拟管程

​ 关于信号量集,可以参考这篇博客:Java并发编程模拟信号量集

​ 下面我们来看代码:

//管程提供的请求资源的方法
public static void getResource(Integer id, SemaphoreUnit unit) throws InterruptedException {
  //保证同一时刻只能有一个线程进入管程
  lock.lock();
  log.info("进程" + id + "请求资源数量:" + unit.getD());
  //构建请求向量
  RequestSource request = new RequestSource();
  request.setId(id);
  request.setSource(Arrays.asList(unit.getD()));

  //这里使用while是防止线程阻塞,再次被唤醒时绕过资源申请
  //对此有疑问的可以参考 JUC模拟AND型信号量一文
  while (true) {
    try {
      //判断银行家算法是否将资源分配给进程
      if (bankerAlgorithm(request)) {
        //银行家算法讲资源分配给进程,需申请掉对应的信号量
        Swait("P" + id, unit);
        //申请到资源,跳出循环
        break;
      } else {
        //未分配,进程阻塞
        condition.await();
        System.out.println("进程P" + id + "被唤醒");
      }
    } catch (Exception e) {
      log.error("无法申请资源,异常原因为:【{}】", e.getMessage());

      //资源申请失败,是进程阻塞
      condition.await();
      System.out.println("进程P" + id + "被唤醒");
    }
  }

  lock.unlock();
}

//管程提供的请求资源的方法
public static void releaseResource(Integer id, SemaphoreUnit unit) throws InterruptedException {
  //保证同一时刻只能有一个线程进入管程
  lock.lock();
  log.info("进程P" + id + "释放资源数量:" + unit.getD() + "");
  Ssignal("P" + id, unit);
  log.info("释放后可用资源的数量:【{}】", tapeDrives.availablePermits());
  //设置银行家算法中对应的资源数量
  //使用回滚资源的现成方法
  RequestSource request = new RequestSource();
  request.setId(id);
  request.setSource(Arrays.asList(unit.getD()));
  rollbackAllocation(request);
  condition.signal();
  lock.unlock();
}

​ 因为银行家算法只能决定是不是可以分配,所以真正的资源分配任务在这里还需要管程来解决,并且资源不足的情况下,还需阻塞线程。

​ 真正去申请资源(信号量),我们使用信号量集的机制来一次申请多个,代码如下,此代码在模拟信号量集中详细讲述过,故不再赘述。

import com.zzxy.com_zzxy.entity.SemaphoreUnit;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 信号量集
 */
@Slf4j
public class SemaphoreSet {
  static final Semaphore mutex = new Semaphore(1);

  static List<Integer> buffer = new ArrayList<>();

  static final Semaphore empty = new Semaphore(50);
  static final Semaphore full = new Semaphore(0);

  static Lock lock = new ReentrantLock();
  static Condition condition = lock.newCondition();

  static Integer count = 0; 

  public static void Swait(String id, SemaphoreUnit... list) throws InterruptedException {
    lock.lock();

    while (true) {
      //如果资源小于分配下限值,就挂起线程,并将线程插入到condition的队列中
      int count = 0;
      for (SemaphoreUnit unit : list) {
        //log.info("当前信号量的状态:【{}】", unit);
        if (unit.getSemaphore().availablePermits() >= unit.getT()) {
          count++;
        } else {
          break;
        }
      }
      if (count == list.length) {
        break;
      }
      log.info("线程【{}】被挂起。", id);
      condition.await();
      log.info("被挂起的线程【{}】被唤醒执行。", id);
    }

    //此处因为只有一个资源,故特殊处理
    log.info("为线程【{}】分配资源{}!", id, list[0].getD());
    for (SemaphoreUnit unit : list) {
      unit.getSemaphore().acquire(unit.getD());
    }

    lock.unlock();
  }

  public static void Ssignal(String id, SemaphoreUnit... list) throws InterruptedException {
    log.info("线程【{}】执行了释放资源", id);
    lock.tryLock(1, TimeUnit.SECONDS);
    //释放资源
    for (SemaphoreUnit unit : list) {
      unit.getSemaphore().release(unit.getD());
    }
    //唤醒等待队列中的线程
    condition.signal();
    lock.unlock();
    log.info("线程【{}】资源释放完毕", id);
  }
}

​ 信号量集申请资源的实体类:

import lombok.Data;
import java.util.concurrent.Semaphore;

@Data
public class SemaphoreUnit {
    //申请的信号量类型
    Semaphore semaphore;
    //分配下限
    Integer t;
    //申请的数量
    Integer d;
}

2.线程的设计与初始化

​ 这部分主要有两部分工作,线程的设计即如何让线程分两次申请资源,初始化工作就比较复杂了,其需要让线程创建时生命最大资源数,并更新银行家算法依赖的三个全局变量:max,allocation,need,对于可用资源available在更早时进程初始化工作。

static class Process extends Thread {
  //线程所需最大资源
  Integer max;
  //用与标注线程第一次申请的资源数
  Integer allocation;
  //线程编号,标明是第几个进入OS的线程,
  //也用于标注线程对应的资源在max,allocation,need几个矩阵中的位置
  Integer id;

  Process(int currentMax, int allocation) {
    this.max = currentMax;
    this.allocation = allocation;
    //线程初始化,第一次去申请allocation数量的资源
    //并返回进程在OS中资源的位置
    this.id = initProcess(Arrays.asList(currentMax));
    System.out.println("初始化后id:" + id + ", name:P" + id);
    super.setName("P" + id);
  }

  @Override
  public void run() {
    do {
      try {
        //首先请求allocation个资源
        SemaphoreUnit unit = new SemaphoreUnit();
        unit.setSemaphore(tapeDrives);
        unit.setT(allocation);
        unit.setD(allocation);
        printResourceAllocationPic();
        getResource(id, unit);
        printResourceAllocationPic();
        //Thread.sleep(1000);
        //第二次申请剩余所需的全部资源
        unit.setD(2);
        unit.setT(2);
        getResource(id, unit);
        if(max - allocation - 2 > 0){
          unit.setD(max - allocation -2);
          unit.setT(max - allocation -2);
          getResource(id, unit);
          printResourceAllocationPic();
          //Thread.sleep(5000);
        }
        //执行完毕,释放所有已经申请的资源
        unit.setD(max);
        releaseResource(id, unit);
        printResourceAllocationPic();
        Thread.sleep(100);
      } catch (InterruptedException e) {
        log.error("消费消息时产生异常!", e);
      }
    } while (true);
  }
}

//线程进入系统,申明自己所需的最大资源数,初始化资源
public static int initProcess(Integer maxR) {
  int id = max.size();
  max.add(maxR);
  List<Integer> currentAllocation = new ArrayList<>();
  for (int i = 0; i < maxR.size(); i++) {
    currentAllocation.add(0);
  }
  allocation.add(currentAllocation);
  List<Integer> needR = new ArrayList<>();
  for (int i = 0; i < maxR.size(); i++) {
    needR.add(maxR.get(0));
  }
  need.add(needR);
  return id;
}

​ 这里有一点需要注意,Arrays.asList不要使用idea推荐的Collections.singletonList(),不然会报错。

​ 为了与银行家算法产生对比,我们同样设计了一个进程ProcessB,此进程不使用银行家算发,直接使用信号量集提供的Swait方法申请资源,代码如下,为保证线程之间的可以轮流执行以抢占资源,因此每次申请资源成功后线程睡眠100ms,让其他线程抢到锁执行。

static class ProcessB extends Thread {
  //线程所需最大资源
  Integer max;
  //用与标注线程第一次申请的资源数
  Integer allocation;

  ProcessB(String name, int currentMax, int allocation) {
    super.setName(name);
    //线程申明所需的最大资源
    this.max = currentMax;
    //线程初始化,第一次去申请allocation数量的资源
    this.allocation = allocation;
  }

  @Override
  public void run() {
    do {
      try {
        //首先请求allocation个资源
        SemaphoreUnit unit = new SemaphoreUnit();
        unit.setSemaphore(tapeDrives);
        unit.setT(allocation);
        unit.setD(allocation);
        Swait(getName(), unit);
        Thread.sleep(100);
        //第二次申请2个资源
        unit.setD(2);
        unit.setT(2);
        Swait(getName(), unit);
        Thread.sleep(100);
        //第三次申请剩余全部资源
        if(max - allocation - 2 > 0){
          unit.setD(max - allocation - 2);
          unit.setT(max - allocation - 2);
          Swait(getName(), unit);
          Thread.sleep(100);
        }
        //执行完毕,释放所有已经申请的资源
        unit.setD(max);
        Ssignal(getName(), unit);
        Thread.sleep(100);
      } catch (InterruptedException e) {
        log.error("消费消息时产生异常!", e);
      }
    } while (true);
  }
}

3.实验结果对比

​ 首先我们执行采用ProcessB创建的线程,执行结果如下所示,三个线程前期快速的交替执行,当时很快,几个线程便相继阻塞,在截图最下方我们可以看到,在此时刻,进程P0占有资源7(5+2),P2占有资源4,此时系统中的可用资源为1,我们可以轻易的看到,此时OS处于不安全状态,几个线程也都阻塞,死锁的产生无法避免。

资源分配图

​ 其次,我们使用Process创建线程,此时线程中采用应用了银行家算法的管程来获取资源,可以使OS避免进入不安全状态,线程便可一直执行下去,因次,部分运行结果如下:

资源分配图

4.总结

​ 好久没写并发编程了,这次写又踩了之前踩过的坑,😹,还好问题不大,运行结果也都顺利的模拟出来了。本文中使用的例子是为了更容易发生死锁而设计的,因此也更典型,喜欢的朋友可以自己把代码复制走在本地跑一跑。

​ 实验运行环境:Java,lombok(如果没有可以自己生成get/set方法,log打印日志也可更改成sout)。

5.完整代码

​ 在这里,把整个银行家算法贴出来(信号量集的代码在第一部分已经完整的贴出来了),代码仅供参考,有什么问题欢迎评论区留言。

​ 其中RequestSource等和银行家算法的部分代码,请参考上篇Java实现银行家算法一文。本例中,为了方便测试,所有的方法都改为了静态方法。

import com.zzxy.com_zzxy.entity.RequestSource;
import com.zzxy.com_zzxy.entity.SemaphoreUnit;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.zzxy.com_zzxy.SemaphoreSetTest.Ssignal;
import static com.zzxy.com_zzxy.SemaphoreSetTest.Swait;

/**
 * 银行家算法
 */
@Slf4j
public class BankerJUCTest {

  //可利用资源变量
  //初始化OS中可用资源的数量
  private static List<Integer> available = new ArrayList<Integer>() {{
    add(12);
  }};

  //最大资源需求矩阵
  private static List<List<Integer>> max = new ArrayList<>();

  //资源分配矩阵
  private static List<List<Integer>> allocation = new ArrayList<>();

  //仍需资源需求矩阵
  private static List<List<Integer>> need = new ArrayList<>();

  //磁带机
  static Semaphore tapeDrives = new Semaphore(12);

  static Lock lock = new ReentrantLock();
  static Condition condition = lock.newCondition();

  //线程进入系统,申明自己所需的最大资源数,初始化资源
  public static int initProcess(List<Integer> maxR) {
    int id = max.size();
    max.add(maxR);
    List<Integer> currentAllocation = new ArrayList<>();
    for (int i = 0; i < maxR.size(); i++) {
      currentAllocation.add(0);
    }
    allocation.add(currentAllocation);
    List<Integer> needR = new ArrayList<>();
    for (int i = 0; i < maxR.size(); i++) {
      needR.add(maxR.get(0));
    }
    need.add(needR);
    return id;
  }

  //管程提供的请求资源的方法
  public static void getResource(Integer id, SemaphoreUnit unit) throws InterruptedException {
    //保证同一时刻只能有一个线程进入管程
    lock.lock();
    log.info("进程" + id + "请求资源数量:" + unit.getD());
    //构建请求向量
    RequestSource request = new RequestSource();
    request.setId(id);
    request.setSource(Arrays.asList(unit.getD()));

    while (true) {
      try {
        //判断银行家算法是否将资源分配给进程
        if (bankerAlgorithm(request)) {
          //银行家算法讲资源分配给进程,需申请掉对应的信号量
          Swait("P" + id, unit);
          //申请到资源,跳出循环
          break;
        } else {
          //未分配,进程阻塞
          condition.await();
          System.out.println("进程P" + id + "被唤醒");
        }
      } catch (Exception e) {
        log.error("无法申请资源,异常原因为:【{}】", e.getMessage());

        //资源申请失败,是进程阻塞
        condition.await();
        System.out.println("进程P" + id + "被唤醒");
      }
    }

    lock.unlock();
  }

  //管程提供的请求资源的方法
  public static void releaseResource(Integer id, SemaphoreUnit unit) throws InterruptedException {
    //保证同一时刻只能有一个线程进入管程
    lock.lock();
    log.info("进程P" + id + "释放资源数量:" + unit.getD() + "");
    Ssignal("P" + id, unit);
    log.info("释放后可用资源的数量:【{}】", tapeDrives.availablePermits());
    //设置银行家算法中对应的资源数量
    //使用回滚资源的现成方法
    RequestSource request = new RequestSource();
    request.setId(id);
    request.setSource(Arrays.asList(unit.getD()));
    rollbackAllocation(request);
    condition.signal();
    lock.unlock();
  }

  //银行家算法
  public static boolean bankerAlgorithm(RequestSource request) throws Exception {
    //请求资源进程的编号
    int id = request.getId();
    //判断请求资源是否超过进程所声明的最大需求数
    for (int i = 0; i < available.size(); i++) {
      if (request.getSource().get(i) > need.get(id).get(i)) {
        throw new Exception("进程" + id + "请求资源超过其申明的最大值,发生异常");
      }
    }
    //判断OS当前是否可以满足进程此次的资源请求
    for (int i = 0; i < available.size(); i++) {
      if (request.getSource().get(i) > available.get(i)) {
        throw new Exception("当前OS尚无足够的资源满足进程" + id + "请求资源,发生异常");
      }
    }
    //进行资源的试探分配
    resourceAllocation(request);
    //进行安全性检查
    if (!securityCheck()) {
      rollbackAllocation(request);
      System.out.println("进程P" + id + "申请资源" + request.getSource() + "不可分配!");
      return false;
    } else {
      System.out.println("进程P" + id + "申请资源" + request.getSource() + "分配成功!");
    }
    return true;
  }

  //资源分配,直接分配,如果安全性检查不通过,进行资源回滚释放
  private static void resourceAllocation(RequestSource request) {
    //请求资源进程的编号
    int id = request.getId();
    List<Integer> requestSource = request.getSource();
    //当前进程已经分配的资源
    List<Integer> currentAllocation = allocation.get(id);
    //当前进程仍需的资源
    List<Integer> currentNeed = need.get(id);

    //修改可利用资源数量、已分配资源、仍需求资源
    //注:因为在前面已经判断过request<=need和available,所以资源不会变成负,不需要处理异常
    for (int i = 0; i < available.size(); i++) {
      available.set(i, available.get(i) - requestSource.get(i));
      currentAllocation.set(i, currentAllocation.get(i) + requestSource.get(i));
      currentNeed.set(i, currentNeed.get(i) - requestSource.get(i));
    }
    //更新总的分配矩阵
    allocation.set(id, currentAllocation);
    //更新总的需求矩阵
    need.set(id, currentNeed);
  }

  //安全性检查算法
  private static boolean securityCheck() {
    //安全性算法检查前打印资源--反应试探分配后的资源情况
    System.out.println("进行安全性检验前的资源分配情况:");
    printResourceAllocationPic();
    //步骤1:初始化临时变量work和
    List<Integer> work = new ArrayList<>();
    work.addAll(available);
    int processCount = max.size();
    List<Boolean> finish = new ArrayList<>();
    for (int i = 0; i < processCount; i++) {
      finish.add(false);
    }
    //i表示已执行多少个进程,id表示可执行的进程号,j表示资源的类型
    int i, j, id;
    //在步骤1中是否找到一个能满足条件的进程,即是否有进程可以执行完毕,释放资源
    boolean flag;
    //步骤1:从进程集合中找到满足条件的进程
    for (i = 0; i < processCount; i++) {
      flag = false;
      for (id = 0; id < processCount; id++) {
        //finish为true表示可以获得所需资源,顺利执行完毕
        if (finish.get(id)) {
          continue;
        }
        List<Integer> currentNeed = need.get(id);
        //j表示资源的类型
        for (j = 0; j < work.size(); j++) {
          if (currentNeed.get(j) > work.get(j)) {
            break;
          }
        }
        //当前进程id所需的资源可以得到满足,转而执行步骤2
        if (j == work.size()) {
          //步骤2
          List<Integer> currentAllocation = allocation.get(id);
          for (j = 0; j < work.size(); j++) {
            //进程可以顺利完成,释放资源
            work.set(j, work.get(j) + currentAllocation.get(j));

          }
          //更改标志位
          finish.set(id, true);
          flag = true;
          //跳出循环,执行步骤1
          System.out.println("安全性算法中此进程执行完毕:P" + id);
          break;
        }
      }
      //上接break
      //判断是否进程分配资源,扫描进程集合一遍,如为分配资源,即表示没有进程可以继续执行,跳出循环,转而执行步骤3
      if (!flag) {
        break;
      }
    }

    //步骤3:判断是否所有进程的finish标志位是否全为true
    for (id = 0; id < processCount; id++) {
      //如果有一个为false,则处于不安全状态
      if (!finish.get(id)) {
        return false;
      }
    }
    return true;
  }

  //安全性检查不通过,回滚刚才分配的资源
  private static void rollbackAllocation(RequestSource request) {
    //请求资源进程的编号
    int id = request.getId();
    List<Integer> requestSource = request.getSource();
    //当前进程已经分配的资源
    List<Integer> currentAllocation = allocation.get(id);
    //当前进程仍需的资源
    List<Integer> currentNeed = need.get(id);

    //修改可利用资源数量、已分配资源、仍需求资源
    //注:因为在前面已经判断过request<=need和available,所以资源不会变成负,不需要处理异常
    for (int i = 0; i < available.size(); i++) {
      available.set(i, available.get(i) + requestSource.get(i));
      currentAllocation.set(i, currentAllocation.get(i) - requestSource.get(i));
      currentNeed.set(i, currentNeed.get(i) + requestSource.get(i));
    }
    //更新总的分配矩阵
    allocation.set(id, currentAllocation);
    //更新总的需求矩阵
    need.set(id, currentNeed);
  }

  private static synchronized void printResourceAllocationPic() {
    for (int i = 0; i < max.size(); i++) {
      System.out.println(max.get(i) + " " + allocation.get(i) + " " + need.get(i));
    }
    System.out.println("当前可用资源:" + available);
  }

  static class Process extends Thread {
    //线程所需最大资源
    Integer max;
    //用与标注线程第一次申请的资源数
    Integer allocation;
    //线程编号,标明是第几个进入OS的线程,
    //也用于标注线程对应的资源在max,allocation,need几个矩阵中的位置
    Integer id;

    Process(int currentMax, int allocation) {
      this.max = currentMax;
      this.allocation = allocation;
      //线程初始化,第一次去申请allocation数量的资源
      //并返回进程在OS中资源的位置
      this.id = initProcess(Arrays.asList(currentMax));
      System.out.println("初始化后id:" + id + ", name:P" + id);
      super.setName("P" + id);
    }

    @Override
    public void run() {
      do {
        try {
          //首先请求allocation个资源
          SemaphoreUnit unit = new SemaphoreUnit();
          unit.setSemaphore(tapeDrives);
          unit.setT(allocation);
          unit.setD(allocation);
          printResourceAllocationPic();
          getResource(id, unit);
          printResourceAllocationPic();
          Thread.sleep(100);
          //第二次申请剩余所需的全部资源
          unit.setD(2);
          unit.setT(2);
          getResource(id, unit);
          Thread.sleep(100);
          if(max - allocation - 2 > 0){
            unit.setD(max - allocation -2);
            unit.setT(max - allocation -2);
            getResource(id, unit);
            printResourceAllocationPic();
            Thread.sleep(100);
          }
          //执行完毕,释放所有已经申请的资源
          unit.setD(max);
          releaseResource(id, unit);
          printResourceAllocationPic();
          Thread.sleep(100);
        } catch (InterruptedException e) {
          log.error("消费消息时产生异常!", e);
        }
      } while (true);
    }
  }

  static class ProcessB extends Thread {
    //线程所需最大资源
    Integer max;
    //用与标注线程第一次申请的资源数
    Integer allocation;

    ProcessB(String name, int currentMax, int allocation) {
      super.setName(name);
      this.max = currentMax;
      this.allocation = allocation;
      //线程初始化,第一次去申请allocation数量的资源
      //并返回进程在OS中资源的位置
    }

    @Override
    public void run() {
      do {
        try {
          //首先请求allocation个资源
          SemaphoreUnit unit = new SemaphoreUnit();
          unit.setSemaphore(tapeDrives);
          unit.setT(allocation);
          unit.setD(allocation);
          Swait(getName(), unit);
          //Thread.sleep(1000);
          //第二次申请2个资源
          unit.setD(2);
          unit.setT(2);
          Swait(getName(), unit);
          //Thread.sleep(2000);
          //第三次申请剩余全部资源
          if(max - allocation - 2 > 0){
            unit.setD(max - allocation - 2);
            unit.setT(max - allocation - 2);
            Swait(getName(), unit);
            //Thread.sleep(5000);
          }
          //执行完毕,释放所有已经申请的资源
          unit.setD(max);
          Ssignal(getName(), unit);
          Thread.sleep(100);
        } catch (InterruptedException e) {
          log.error("消费消息时产生异常!", e);
        }
      } while (true);
    }
  }

  public static void main(String[] args) throws Exception {
    Process p0 = new Process(10, 5);
    Process p1 = new Process(4, 2);
    Process p2 = new Process(9, 2);

    //ProcessB p0 = new ProcessB("P0", 10, 5);
    //ProcessB p1 = new ProcessB("P1", 4, 2);
    //ProcessB p2 = new ProcessB("P2", 9, 2);

    p0.start();
    p1.start();
    p2.start();
  }
}

​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​ 如有兴趣,还可以查看我的其他几篇博客,都是OS的干货,喜欢的话还请点赞、评论加关注_

操作系统武功修炼心法

展开阅读全文
©️2020 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值