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、造成死锁原理图

在这里插入图片描述

问题代码
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();
                }
         }
    }
  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值