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(); } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。