当前位置:龙哥网 > 网络技术 > 关于Process的waitFor死锁问题及解决方案_java(process.waitfor)

关于Process的waitFor死锁问题及解决方案_java(process.waitfor)

longge3个月前 (03-01)网络技术52
目录
  • Process的waitFor死锁问题
    • 1、发现问题
    • 2、原因查找
    • 3、造成死锁原理图
      • 问题代码
      • 解决方案
  • java中死锁的简单例子及其避免
    • 我们来看一个死锁的简单例子
      • 那么要怎么预防死锁呢?

      Process的waitFor死锁问题

      1、发现问题

      在实际开发中,进行文件操作时,使用Process对文件进行解压操作,程序执行过程中会出现一直卡在那的问题。

      2、原因查找

      问题在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。

      程序运行过程产生等待分析:

      (1)、当我们使用Runtime.exec执行命令时,JAVA的线程会创建一个子进程,用于执行命令,而且子进程和JAVA线程会分别独立运行。

      (2)、JAVA线程需要等待命令的执行完成,对命令的日志和返回值进行处理,所以在JAVA线程中调用Process.waitFor挂起来等待子进程完成。

      (3)、子线程执行时,不断的打印日志信息,我们通过Process.getInputStream和Process.getErrorStream进行获取正常输出日志和错误日志进行处理,也即是读数据。

      (4)、这个时候子进程不断的向JAVA线程写入数据,而JAVA线程调用Process.waitFor后已经阻塞挂起,而子进程在不断的向JAVA线程进行写入数据,当我们的Process.getInputStream的buffer缓冲区被写满,JAVA线程依然挂起并未消费buffer中的数据,导致子进程无法继续向buffer缓冲区中继续写入数据,导致子进程也挂起。

      (5)、这个时候JAVA线程和子进程都处于挂起的状态,JAVA线程等待子进程的结束,子进程等待JAVA线程对buffer缓冲区中的数据进行消费。两者在相互等待导致死锁。

      3、造成死锁原理图

      关于Process的waitFor死锁问题及解决方案_java(process.waitfor)

      问题代码

      public static String cmd(String cmd) throws InterruptedException  {
              try {
                      // 使用Runtime来执行command,生成Process对象
                      Process process = Runtime.getRuntime().exec(
                              new String[] { "/bin/sh", "-c", cmd });
                      int exitCode = process.waitFor();
                      // 取得命令结果的输出流
                      InputStream is = process.getInputStream();
                      // 用一个读输出流类去读
                      InputStreamReader isr = new InputStreamReader(is);
                      // 用缓冲器读行
                      BufferedReader br = new BufferedReader(isr);
                      String line = null;
                      StringBuilder sb = new StringBuilder();
                      while ((line = br.readLine()) != null) {
                          sb.append(line);
                      }
                      is.close();
                      isr.close();
                      br.close();
                      
                      return sb.toString();
                  } catch (java.lang.NullPointerException e) {
                  	
                  	throw new BaseException(e);
                  } catch (java.io.IOException e) {
                  	throw new BaseException(e);
                  }
                  return null;
          }
      

      这段代码会产生死锁的原因是, BufferedReader br = new BufferedReader(isr);放在了process.waitFor();之后执行,并不能有效的清空缓冲区,还是会造成缓冲区满,造成阻塞。

      解决方案

      知道问题原因就号解决了

      public static String callCmd(String cmd) throws InterruptedException  {
               Process process = null;
               BufferedReader normalReader = null;
               BufferedReader errorReader = null;
               try {          
                      process = Runtime.getRuntime().exec(
                              new String[] { "/bin/sh", "-c", cmd });
                      /*
                          启动读取buffer缓冲区子线程,避免buffer缓冲区被写满,导致线程等待问题
                       */
                      normalReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                      errorReader = new  BufferedReader(new  InputStreamReader(process.getErrorStream()));
                      String line = null;
                      StringBuilder sb = new StringBuilder();
                      while ((line = normalReader.readLine()) != null) {
                          sb.append(line);
                      }
                      String errorLine;
                      while ((errorLine = errorReader.readLine()) != null) {
                          logger.warn("脚本文件执行信息ErrorStream:{}", errorLine);
                      }
                      // 等待程序执行结束并输出状态
                      int exitCode = process.waitFor();
                      if (0 == exitCode) {
                          logger.info("脚本文件执行成功!");
                      } else {
                          logger.error("脚本文件执行失败:{}",cmd);
                          throw new BaseException("脚本文件执行失败:"+cmd);
                      }
                      return sb.toString();
                } catch (java.lang.NullPointerException e) {
                      throw new BaseException(e);
                } catch (java.io.IOException e) {
                      throw new BaseException(e);
                }finally{
                      if (null != normalReader) {
                          try {
                              normalReader.close();
                          } catch (IOException e) {
                              logger.error("流文件关闭异常:", e);
                          }
                      }
                      if (null != errorReader) {
                          try {
                              errorReader.close();
                          } catch (IOException e) {
                              logger.error("流文件关闭异常:", e);
                          }
                      }
                      if (null != process) {
                          process.destroy();
                      }
               }
          }
      

      Java中死锁的简单例子及其避免

      死锁:当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。

      比如,线程1已经持有了A锁并想要获得B锁的同时,线程2持有B锁并尝试获取A锁,那么这两个线程将永远地等待下去。

      我们来看一个死锁的简单例子

      public class DeadLockTest
      {
          private static Object A = new Object(), B = new Object();
          public static void main(String[] args)
          {
              new Thread(() -> {
                  System.out.println("线程1开始执行...");
                  synchronized (A)
                  {
                      try
                      {
                          System.out.println("线程1拿到A锁");
                          //休眠两秒让线程2有时间拿到B锁
                          Thread.sleep(2000);
                      } catch (Exception e)
                      {
                          e.printStackTrace();
                      }
                      synchronized (B)
                      {
                          System.out.println("线程1拿到B锁");
                      }
                  }
              }).start();
              new Thread(() -> {
                  System.out.println("线程2开始执行...");
                  synchronized (B)
                  {
                      try
                      {
                          System.out.println("线程2拿到B锁");
                          //休眠两秒让线程1有时间拿到A锁
                          Thread.sleep(2000);
                      } catch (Exception e)
                      {
                          e.printStackTrace();
                      }
                      synchronized (A)
                      {
                          System.out.println("线程2拿到A锁");
                      }
                  }
              }).start();
          }
      }

      运行结果:

      关于Process的waitFor死锁问题及解决方案_java(process.waitfor)

      从运行结果可看到,线程1拿到了A锁,并尝试去获取B锁,与此同时线程2拿到了B锁并尝试去获取A锁,此时线程1和线程2就陷入了无限的等待,形成死锁。

      那么要怎么预防死锁呢?

      下面介绍几个常见方法:

      1、避免一个线程同时获取多个锁

      2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源

      3、尝试使用定时锁,使用lock.tryLock来代替使用内置锁。

      免责声明
      本站部分资源来源于互联网 如有侵权 请联系站长删除
      龙哥网是优质的互联网科技创业资源_行业项目分享_网络知识引流变现方法的平台为广大网友提供学习互联网相关知识_内容变现的方法。#转载请注明出处!