一、概述
在编写Java程序时,有时候我们需要调用外部的exe,这时可以通过Runtime的exec()方法,
该命令使用比较简单,但是无法对执行做细节的控制。
通过使用ProcessBuilder构建Process可以进行细节的控制,但是默认process是阻塞的。
public abstract int waitFor() throws InterruptedException;
public boolean waitFor(long timeout, TimeUnit unit)
不要被boolean waitFor(long timeout, TimeUnit unit)误解,他也是阻塞的,阻塞到process进程执行完,才进行是否超时的判断。
并不是说当超时时他会自动结束。
通过process.destroy()方法可以从外部终止该process。
但是waitFor时主进程阻塞是阻塞的。我们需要在外部开启一条线程,把waitFor这个阻塞操作放到这条线程,同时主线程进行计时,
如果到了限定时间还未执行完毕,那么就调用process.destroy() kill掉该进程。
二、具体代码思路
首先是一个ProcessWorker类,也就是之前说的用来放置waitFor,阻塞的线程,它继承Thread,实现了run方法。
它记录以下几个状态,通过volatile来确保外部线程可见:
private volatile int exitCode = -99;
private volatile boolean completed = false;
private volatile String output = "";
在run方法中,通过try with resource来读取该进程的输出,
并且设置waitFor,当等待结束时,设置completed为true。
try (InputStreamReader reader = new InputStreamReader(
process.getInputStream(), DEFAULT_ENCODING)) {
StringBuilder log = new StringBuilder();
char[] buffer = new char[BUFFER_SIZE];
int length;
while ((length = reader.read(buffer)) != -1) {
log.append(buffer, 0, length);
}
output = log.toString();
exitCode = process.waitFor();
completed = true;
} catch (InterruptedException | IOException e) {
Thread.currentThread().interrupt();
}
三、完整代码如下
package cn.hengyumo.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* SystemUtil
* 系统工具类
*
* @version 1.0
* @since 2020/2/4
*/
public class SystemUtil {
private final static int BUFFER_SIZE = 1024;
private final static String DEFAULT_ENCODING = "gbk";
private static class ProcessWorker extends Thread {
private final Process process;
private volatile int exitCode = -99;
private volatile boolean completed = false;
private volatile String output = "";
private ProcessWorker(Process process) {
this.process = process;
}
@Override
public void run() {
try (InputStreamReader reader = new InputStreamReader(
process.getInputStream(), DEFAULT_ENCODING)) {
StringBuilder log = new StringBuilder();
char[] buffer = new char[BUFFER_SIZE];
int length;
while ((length = reader.read(buffer)) != -1) {
log.append(buffer, 0, length);
}
output = log.toString();
exitCode = process.waitFor();
completed = true;
} catch (InterruptedException | IOException e) {
Thread.currentThread().interrupt();
}
}
public int getExitCode() {
return exitCode;
}
public String getOutput() {
return output;
}
public boolean isCompleted() {
return completed;
}
}
public static int execCmd(String command, StringBuilder log, int timeoutSecond) throws IOException, TimeoutException {
ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
// 合并错误输出流
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
ProcessWorker processWorker = new ProcessWorker(process);
int exitCode = processWorker.getExitCode();
processWorker.start();
try {
processWorker.join(timeoutSecond * 1000);
if (processWorker.isCompleted()) {
log.append(processWorker.getOutput());
exitCode = processWorker.getExitCode();
} else {
process.destroy();
processWorker.interrupt();
throw new TimeoutException("进程执行时间超时");
}
} catch (InterruptedException e) {
processWorker.interrupt();
}
return exitCode;
}
}