前言:
目前大家对“java执行exec”都比较关切,我们都需要分析一些“java执行exec”的相关知识。那么小编同时在网络上搜集了一些有关“java执行exec””的相关资讯,希望各位老铁们能喜欢,你们一起来学习一下吧!坚持原创,共同进步!请关注我,后续分享更精彩!背景
项目中使用到Runtime.getRuntime().exec(command)和外部程序的跨进程交互。
遇到以下问题:
调用线程间隙性被阻塞,不返回结果。线程并发量增加,部分线程假死,导致资源一直不释放。
经过优化,整理记录下来,给遇到同样问题的小伙伴。
代码实现
执行结果返回类:
@Datapublic static class ExecResult<S>{ private boolean status; private S successResult; private String errorResult;}
执行任务接口:
/** * 运行时执行任务接口 */public interface RuntimeExecTask<S>{ /** * 处理命令执行返回的输入流 * @param inputStream */ S handleInputStream(InputStream inputStream); /** * 处理命令执行错误结果 * @param error */ String handleErrorStream(String error);}
RuntimeExecCommander类exec执行方法:
/** * 执行运行时命令返回 * @param command 执行命令 * @param task 命令执行结果处理任务 * @param timeout 超时时间 * @param unit 超时时间单位 */public <S> ExecResult<S> exec(String command,long timeout, TimeUnit unit,RuntimeExecTask<S> task){ if (StringUtils.isBlank(command)) { throw new RuntimeException("command is not allowed null."); } ExecutorService pool = Executors.newFixedThreadPool(2); Process process = null; ExecResult<S> execResult = new ExecResult<>(); try { process = Runtime.getRuntime().exec(command); //避免process被hang住,需要新开线程处理buffer stream数据 Process finalProcess = process; Future<String> errorFutureResult = pool.submit(() -> task.handleErrorStream(write2String(finalProcess.getErrorStream()))); Future<S> successFutureResult = pool.submit(() -> task.handleInputStream(finalProcess.getInputStream())); if (!process.waitFor(timeout, unit)) { //超时处理 execResult.setStatus(false); execResult.setErrorResult("timeout is occurred. timeout="+unit.toSeconds(timeout)+"ms"); return execResult; } String errorResult = errorFutureResult.get(1,TimeUnit.SECONDS); S successResult = successFutureResult.get(1,TimeUnit.SECONDS); if (StringUtils.isNotBlank(errorResult)) { execResult.setStatus(false); execResult.setErrorResult(errorResult); }else{ execResult.setStatus(true); execResult.setSuccessResult(successResult); } return execResult; } catch (Exception e) { log.error("runtime command exec error:",e); execResult.setStatus(false); execResult.setErrorResult("运行时命令执行错误:"+e.getMessage()); return execResult; }finally { try { if (process!=null) { process.destroy(); } pool.shutdown(); pool.awaitTermination(1,TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } }}
说明:
1.Runtime.getRuntime().exec(command) 命令执行后会启动新的目标调用进程。java中的当前线程和新的目标调用进程间,通过error和input流进行结果反馈。error和input流读取时有一个buffer缓存区,当缓存区填满,java线程迟迟未读取时,java 线程和目标进程将被阻塞。所以上面代码20、21行处,需新开线程实时监控处理,避免程序被阻塞。
2.经过反复测试,即便添加第1条的机制。并发线程上来(50线程压测),10-20%调用会假死,一直不释放资源。为解决这个问题,通过timeout超时机制来避免资源一直被占用(代码23行)。最后49行代码回收销毁进程资源。
RuntimeExecCommander类流处理辅助方法:
private String write2String(InputStream inputStream){ StringBuilder sb = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = null; try { while ((line = bufferedReader.readLine())!=null){ sb.append(line); } } catch (IOException e) { e.printStackTrace(); } return sb.toString();}
测试方法:
public static void main(String[] args) { String command = "node d:\\working\\workspace\\puppeteer\\page2pdf.js \"\""; for(int i=0;i<50;i++){ int finalI = i; new Thread(() -> { RuntimeExecCommander execCommander = new RuntimeExecCommander(); execCommander.exec(command,10,TimeUnit.MINUTES, new RuntimeExecTask<String>() { @Override public String handleInputStream(InputStream inputStream) { return execCommander.write2String(inputStream); } @Override public String handleErrorStream(String error) { if (error!=null && error.length()>0) { error = "i="+finalI+",错误提示:"+error; } return error; } }); System.out.println("第"+ finalI +"个任务执行完成."); }).start(); }}小结
本文介绍了java中通过Runtime.getRuntime().exec(command)调用外部进程的方法。以及在调用过程中线程被阻塞和假死情况下解决方式。
最后,希望本文对大家有所帮助和参考。若有疑问,欢迎留言讨论。
标签: #java执行exec