java调用process线程阻塞问题的解决

 更新时间:2021年06月09日 08:35:59   作者:遗失的岁月  
这篇文章主要介绍了java调用process线程阻塞问题的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

java调用process线程阻塞问题

项目需求中涉及java调用.bat文件进行图像处理,先直接上简略版程序

public void draw(){
        //调用bat脚本进行图像处理
        Process process = null;
        InputStream in = null;
        try {
            process = Runtime.getRuntime().exec("startup.bat");
            //输出测试
//            in = process.getInputStream();
//            String line;
//            BufferedReader br = new BufferedReader(new InputStreamReader(in));
//            while ((line = br.readLine()) != null) {
//                System.out.println(line);
//            }
            //等待
            process.waitFor();
        } catch (Exception e) {
        } finally {
            process.destroy();
        }
    }

JAVA使用遇到的问题描述

一般需要调用系统命令时,大部分人第一反应肯定是使用Runtime.getRuntime().exec(command)返回一个process对象,再调用process.waitFor()来等待命令执行结束,获取执行结果。

调试的时候发现异常现象,process.waitFor();一直没有结束,导致线程阻塞再次,强行关闭程序后,发现图像处理只进行了一部分。

根据现象并查看了JDK的帮助文档,如下

如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。

对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的阻塞,甚至死锁。

Process执行逻辑

* 主进程中调用Runtime.exec会创建一个子进程,用于执行脚本。子进程创建后会和主进程分别独立运行。

* 创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。

* 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。

* 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

解决方法:

在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;

修改后代码

public class test {
    public void draw(){
        //调用bat脚本进行图像处理
        Process process = null;
        InputStream in = null;
        try {
            process = Runtime.getRuntime().exec("startup.bat");
            //输出测试
//            in = process.getInputStream();
//            String line;
//            BufferedReader br = new BufferedReader(new InputStreamReader(in));
//            while ((line = br.readLine()) != null) {
//                System.out.println(line);
//            }
            //新启两个线程
            new DealProcessSream(process.getInputStream()).start();
            new DealProcessSream(process.getErrorStream()).start();
            process.waitFor();
        } catch (Exception e) {
        } finally {
            process.destroy();
        }
    }
}
public class DealProcessSream extends Thread {
    private InputStream inputStream;
    public DealProcessSream(InputStream inputStream) {
        this.inputStream = inputStream;
    }
    public void run() {
        InputStreamReader inputStreamReader = null;
        BufferedReader br = null;
        try {
            inputStreamReader = new InputStreamReader(
                    inputStream);
            br = new BufferedReader(inputStreamReader);
            // 打印信息
//            String line = null;
//            while ((line = br.readLine()) != null) {
//                System.out.println(line);
//            }
            // 不打印信息
           while (br.readLine() != null);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }finally {
            try {
                br.close();
                inputStreamReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Process对象.waitFor()的阻塞问题(坑)

有时需要在程序中调用可执行程序或脚本命令:

Process process = Runtime.getRuntime().exec(shPath);
int exitCode = process .waitFor();

Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,

并返回与该子进程对应的Process对象实例。通过Process可以控制该子进程的执行或获取该子进程的信息。

它的所有标准io(即stdin,stdout,stderr)操作都将通过三个流(getOutputStream(),getInputStream(),getErrorStream())重定向到父进程。

父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读

写子进程的输出流或输入流出现失败,则可能导致子进程阻塞,甚至产生死锁。(如果程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waifor()这里。)

process .getErrorStream():获得子进程的错误输出流

process .getInputStream():获得子进程的普通输出流

简单示例:

Process shellProcess = null;
    try {
 
      shellProcess = Runtime.getRuntime().exec(shPath);
      shellErrorResultReader = new BufferedReader(new InputStreamReader(shellProcess.getErrorStream()));
      shellInfoResultReader =  new BufferedReader(new InputStreamReader(shellProcess.getInputStream()));
      String infoLine;
      while ((infoLine = shellInfoResultReader.readLine()) != null) {
        logger.info("脚本文件执行信息:{}", infoLine);
      }
      String errorLine;
      while ((errorLine = shellErrorResultReader.readLine()) != null) {
        logger.warn("脚本文件执行信息:{}", errorLine);
      }
      // 等待程序执行结束并输出状态
      exitCode = shellProcess.waitFor();
      if (0 == exitCode) {
        logger.info("脚本文件执行成功:" + exitCode);
      } else {
        logger.error("脚本文件执行失败:" + exitCode);
      }
    } catch (Exception e) {
      logger.error("shell脚本执行错误", e);
    } finally {
      if (null != shellInfoResultReader) {
        try {
          shellInfoResultReader.close();
        } catch (IOException e) {
          logger.error("流文件关闭异常:", e);
        }
      }
      if (null != shellErrorResultReader) {
        try {
          shellErrorResultReader.close();
        } catch (IOException e) {
          logger.error("流文件关闭异常:", e);
        }
      }
      if (null != shellProcess) {
        shellProcess.destroy();
      }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java类中使用Jfreechart的简单实例

    java类中使用Jfreechart的简单实例

    这篇文章介绍了java类中使用Jfreechart的简单实例,有需要的朋友可以参考一下
    2013-08-08
  • JAVA中的队列(Queue)详解

    JAVA中的队列(Queue)详解

    这篇文章主要介绍了JAVA中的队列(Queue)详解,队列是一种特殊的线性表,遵循先入先出、后入后出的基本原则,一般来说,它只允许在表的前端进行删除操作,需要的朋友可以参考下
    2023-07-07
  • 记一次springboot服务凌晨无故宕机问题的解决

    记一次springboot服务凌晨无故宕机问题的解决

    这篇文章主要介绍了记一次springboot服务凌晨无故宕机问题的解决,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Nacos配置中心搭建及动态刷新配置及踩坑记录

    Nacos配置中心搭建及动态刷新配置及踩坑记录

    这篇文章主要介绍了Nacos配置中心搭建及动态刷新配置及踩坑记录,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • java实现带有背景图片的窗体

    java实现带有背景图片的窗体

    这篇文章主要为大家详细介绍了java实现带有背景图片的窗体,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • 如何从request中获取body的数据

    如何从request中获取body的数据

    这篇文章主要介绍了如何从request中获取body的数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • SpringBoot+Quartz+数据库存储的完美集合

    SpringBoot+Quartz+数据库存储的完美集合

    这篇文章主要介绍了SpringBoot+Quartz+数据库存储的示例代码,本文通过实例代码图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • springcloud之Feign、ribbon如何设置超时时间和重试机制

    springcloud之Feign、ribbon如何设置超时时间和重试机制

    这篇文章主要介绍了springcloud之Feign、ribbon如何设置超时时间和重试机制,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 如何解决java.lang.ClassNotFoundException: com.mysql.jdbc.Driver问题

    如何解决java.lang.ClassNotFoundException: com.mysql.jdbc.Dr

    这篇文章主要介绍了如何解决java.lang.ClassNotFoundException: com.mysql.jdbc.Driver问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Rabbitmq消息推送功能实现示例

    Rabbitmq消息推送功能实现示例

    rabbitMQ为异步消息处理提出了一个很好的解决方案,它是一个非常好用的消息中间件。主要解决当生产者大量产生数据时,消费者无法快速消费的问题。这个时候需要一个中间层,保存这个数据,rabbitMQ是一个很好的解决方案
    2022-12-12

最新评论