使用Runtime 调用Process.waitfor导致的阻塞问题
程序员文章站
2022-03-04 14:55:57
目录1. 关于runtime类的小知识2. runtime的几个重要的重载方法3. runtime的使用方式4. 卡死原因5. 解决方案6. runtime最优雅的调用方式1. 关于runtime类的...
1. 关于runtime类的小知识
-
runtime.getruntime()
可以取得当前jvm的运行时环境,这也是在java中唯一一个得到运行时环境的方法 -
runtime
中的exit方法是退出jvm
2. runtime的几个重要的重载方法
方法名 | 作用 |
---|---|
exec(string command); | 在单独的进程中执行指定的字符串命令。 |
exec(string command, string[] envp) | 在指定环境的单独进程中执行指定的字符串命令。 |
exec(string[] cmdarray, string[] envp, file dir) | 在指定环境和工作目录的独立进程中执行指定的命令和变量 |
exec(string command, string[] envp, file dir) | 在有指定环境和工作目录的独立进程中执行指定的字符串命令。 |
runtime类的重要的方法还有很多,简单列举几个
-
exit(int status)
:终止当前正在运行的 java 虚拟机 -
freememory()
:返回 java 虚拟机中的空闲内存量。 -
load(string filename)
: 加载作为动态库的指定文件名。 -
loadlibrary(string libname)
: 加载具有指定库名的动态库。
3. runtime的使用方式
错误的使用exitvalue()
public static void main(string[] args) throws ioexception { string command = "ping www.baidu.com"; process process = runtime.getruntime().exec(command); int i = process.exitvalue(); system.out.println("字进程退出值:"+i); }
输出:
exception in thread "main" java.lang.illegalthreadstateexception: process has not exited
at java.lang.processimpl.exitvalue(processimpl.java:443)
at com.lirong.think.runtime.processutils.main(processutils.java:26)
原因:
exitvalue()方法是非阻塞的,在调用这个方法时cmd命令并没有返回所以引起异常。阻塞形式的方法是waitfor,它会一直等待外部命令执行完毕,然后返回执行的结果。
修改后的版本:
public static void main(string[] args) throws ioexception { string command = "javac"; process process = runtime.getruntime().exec(command); process.waitfor(); process.destroy(); int i = process.exitvalue(); system.out.println("字进程退出值:"+i); }
此版本已然可以正常运行,但当主线程和子线程有很多交互的时候还是会出问题,会出现卡死的情况。
4. 卡死原因
- 主进程中调用runtime.exec会创建一个子进程,用于执行cmd命令。子进程创建后会和主进程分别独立运行。
- 因为主进程需要等待脚本执行完成,然后对命令返回值或输出进行处理,所以这里主进程调用process.waitfor等待子进程完成。
- 运行此cmd命令可以知道:子进程执行过程就是打印信息。主进程中可以通过process.getinputstream和process.geterrorstream获取并处理。
- 这时候子进程不断向主进程发生数据,而主进程调用process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
- 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。
5. 解决方案
不断的读取消耗缓冲区的数据,以至子进程不会挂起,下面是具体代码:
/** * @author lirong * @desc cmd命令测试 * @date 2019/06/13 20:50 */ @slf4j public class processutils { public static void main(string[] args) throws ioexception, interruptedexception { string command = "ping www.baidu.com"; process process = runtime.getruntime().exec(command); readstreaminfo(process.getinputstream(), process.geterrorstream()); int exit = process.waitfor(); process.destroy(); if (exit == 0) { log.debug("子进程正常完成"); } else { log.debug("子进程异常结束"); } } /** * 读取runtime.exec运行子进程的输入流 和 异常流 * @param inputstreams 输入流 */ public static void readstreaminfo(inputstream... inputstreams){ executorservice executorservice = executors.newfixedthreadpool(inputstreams.length); for (inputstream in : inputstreams) { executorservice.execute(new mythread (in)); } executorservice.shutdown(); } } /** * @author lirong * @desc * @date 2019/06/13 21:25 */ @slf4j public class mythread implements runnable { private inputstream in; public mythread(inputstream in){ this.in = in; } @override public void run() { try{ bufferedreader br = new bufferedreader(new inputstreamreader(in, "gbk")); string line = null; while((line = br.readline())!=null){ log.debug(" inputstream: " + line); } }catch (ioexception e){ e.printstacktrace(); }finally { try { in.close(); } catch (ioexception e) { e.printstacktrace(); } } } }
写到这里大家以为都结束了哇,并没有,哈哈哈,真实的生成环境总能给你带来很多神奇的问题,runtime不仅可以直接调用cmd执行命令,还可以调用其他.exe程序执行命令。
所以纵使你读取了缓冲区的数据,你的程序依然可能会被卡死,因为有可能你的缓冲区根本就没有数据,而是你的.exe程序卡主了。嗯,所以为你以防万一,你还需要设置超时。
6. runtime最优雅的调用方式
/** * @author lirong * @desc * @date 2019/06/13 20:50 */ @slf4j public class processutils { /** * @param timeout 超时时长 * @param filedir 所运行程序路径 * @param command 程序所要执行的命令 * 运行一个外部命令,返回状态.若超过指定的超时时间,抛出timeoutexception */ public static int executeprocess(final long timeout, file filedir, final string[] command) throws ioexception, interruptedexception, timeoutexception { process process = runtime.getruntime().exec(command, null, filedir); worker worker = new worker(process); worker.start(); try { worker.join(timeout); if (worker.exit != null){ return worker.exit; } else{ throw new timeoutexception(); } } catch (interruptedexception ex) { worker.interrupt(); thread.currentthread().interrupt(); throw ex; } finally { process.destroy(); } } private static class worker extends thread { private final process process; private integer exit; private worker(process process) { this.process = process; } @override public void run() { inputstream errorstream = null; inputstream inputstream = null; try { errorstream = process.geterrorstream(); inputstream = process.getinputstream(); readstreaminfo(errorstream, inputstream); exit = process.waitfor(); process.destroy(); if (exit == 0) { log.debug("子进程正常完成"); } else { log.debug("子进程异常结束"); } } catch (interruptedexception ignore) { return; } } } /** * 读取runtime.exec运行子进程的输入流 和 异常流 * @param inputstreams 输入流 */ public static void readstreaminfo(inputstream... inputstreams){ executorservice executorservice = executors.newfixedthreadpool(inputstreams.length); for (inputstream in : inputstreams) { executorservice.execute(new mythread(in)); } executorservice.shutdown(); } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)
-
调用Runtime.getRuntime().exec()执行Linux脚本导致程序挂住的问题分析
-
调用Runtime.getRuntime().exec()执行Linux脚本导致程序挂住的问题分析
-
使用Runtime 调用Process.waitfor导致的阻塞问题
-
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)_PHP教程
-
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)_php技巧
-
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)
-
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)_PHP教程
-
使用Runtime 调用Process.waitfor导致的阻塞问题