龙空技术网

Java中Runtime.exec()外部命令执行优化

清风明月eagle 254

前言:

目前大家对“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