坚持原创,共同进步!请关注我,后续分享更精彩!
背景
项目中使用到Runtime.getRuntime().exec(command)和外部程序的跨进程交互。
遇到以下问题:
- 调用线程间隙性被阻塞,不返回结果。
- 线程并发量增加,部分线程假死,导致资源一直不释放。
经过优化,整理记录下来,给遇到同样问题的小伙伴。
代码实现
执行结果返回类:
@Data
public 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 \"https://www.baidu.com\"";
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)调用外部进程的方法。以及在调用过程中线程被阻塞和假死情况下解决方式。
最后,希望本文对大家有所帮助和参考。若有疑问,欢迎留言讨论。
本文暂时没有评论,来添加一个吧(●'◡'●)