据我了解,如果子进程运行时间超过四五秒,您希望停止子进程。这不能直接用ProcessBuilder 完成(您可以看到类中不存在相关方法),但是一旦子流程开始,您就可以轻松实现此行为。
像您在示例代码中那样调用Process.waitFor() 是有问题的,因为它会无限期地阻塞您当前的线程——如果您的进程花费的时间超过五秒,.waitFor() 不会停止它。但是.waitFor() 被重载,它的sibling 接受timeout 参数。
public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
如有必要,使当前线程等待,直到此 Process 对象表示的子进程终止,或指定的等待时间过去。
如果需要太长时间,您可以将其与 Process.destroy() 结合使用来停止该过程。例如:
Process process = new ProcessBuilder(command, and, arguments)
.redirectErrorStream(true)
.directory(workingDir)
.start();
process.waitFor(5, TimeUnit.SECONDS);
process.destroy();
process.waitFor(); // wait for the process to terminate
这依赖于 Process.destroy() 在已完成的子进程上调用时是无操作的事实。在 Java 9 之前,这种行为没有被记录,但在实践中一直如此。另一种方法是检查.waitFor() 的返回值,但这会引入TOCTTOU race。
Process.destroyForcibly() 呢?一般来说,你不应该调用这个方法(JDK 可能更清楚的另一件事),但是如果一个进程真的挂起,它可能变得有必要。理想情况下,您应该确保您的子流程表现良好,但如果您必须使用.destroyForcibly(),我建议您这样做:
// Option 2
process.waitFor(5, TimeUnit.SECONDS); // let the process run for 5 seconds
process.destroy(); // tell the process to stop
process.waitFor(10, TimeUnit.SECONDS); // give it a chance to stop
process.destroyForcibly(); // tell the OS to kill the process
process.waitFor(); // the process is now dead
这确保了行为不端的进程将被及时终止,同时仍然给正确实施的程序在收到指示后有时间退出。 .destroy() 和 .destroyForcibly() 的确切行为是特定于操作系统的,但在 Linux 上我们可以看到 they correspond to SIGTERM and SIGKILL:
int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
kill(pid, sig);
You should rarely have a need to call .destroyForcibly(),我建议仅在您发现有必要时添加它。
选项 2 在概念上类似于使用 timeout 命令,如下所示:
$ timeout --kill-after=10 5 your_command
在 Java 7 中复制 Process.waitFor(long, TimeUnit) 很容易,default Java 8 implementation 没有什么神奇之处:
public boolean waitFor(long timeout, TimeUnit unit)
throws InterruptedException
{
long startTime = System.nanoTime();
long rem = unit.toNanos(timeout);
do {
try {
exitValue();
return true;
} catch(IllegalThreadStateException ex) {
if (rem > 0)
Thread.sleep(
Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
}
rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
} while (rem > 0);
return false;
}