import java.lang.ProcessBuilder.Redirect;
public class Main {
public static void main(String args[]){
ProcessBuilder pb = new ProcessBuilder(args);
try{
Process p = pb.start();
System.out.println("child process is alive: " + p.isAlive());
System.out.println("child process pid: " + p.pid());
System.out.println("parent of child process: " + p.toHandle().parent().get().pid());
System.out.println("child process is alive: " + p.isAlive());
p.waitFor();
} catch (Exception e) {
System.out.println("some exception with start process.");
e.printStackTrace();
}
}
}
在 Ubuntu 18.04 上,我运行该程序以运行外部程序sleep 10
而没有问题:
$ java Main sleep 10
child process is alive: true
child process pid: 20559
parent of child process: 20539
child process is alive: true
$
但是当我运行程序以运行外部程序时echo hello
$ java Main echo hello
child process is alive: false
child process pid: 18534
some exception with start process.
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:148)
at Main.main(Main.java:11)
为什么它会在第 11 行p.toHandle().parent().get().pid()
的get()
处报告错误?
如果我注释掉第 11 行,它将毫无问题地运行。为什么?
如果是因为孩子在第 11 行呼叫p.toHandle().parent().get().pid()
之前退出,
- 为什么
NoSuchElementException
发生在get()
,而不是更早发生在toHandle()
或p.toHandle().parent().get().pid()
的parent()
11号线? - 为什么在10号线呼叫
p.pid()
时没有这样的问题?
我该怎么做才能使问题不发生?例如,如何使子进程持续更长时间,以便p.toHandle().parent().get().pid()
可以工作,即使我在子进程中运行了一个短暂的程序?
为什么使用echo hello
调用时从parent()
返回的可选为空?
因为子进程非常短,并且当您到达请求父进程的命令时,子进程不再存在。因此,无法再查询有关该进程的操作系统。
为什么在呼叫getHandle()
或pid()
时不会发生这种情况?
请注意,这些都不是Optional
。这意味着 API 保证在其中返回值。实际上,实现是这样的,一旦操作系统创建进程并在pb.start()
中返回,就会创建句柄 - 包括进程ID。因此,您有一个句柄,并且您有一个进程 ID - 但当您使用它时,不能保证具有该 ID 的进程处于活动状态。
该文档明确告诉您不要对基础进程的活动性或标识做出假设。
parent()
调用当前通过使用子项的 pid 查询操作系统来实现。因此,创建流程时,父级不是流程记录的一部分,并且在您调用它时,可能不再可用。
如何避免这种情况
每当使用Optional
时,请勿使用get
。特别是没有事先检查它是否存在(并使用isPresent
......get
被认为是"反模式")。当你看到一个API给你一个Optional
,想想如果Optional
恰好是空的,你想做什么。不要做出文档不合理的假设。
在这种情况下,您可能希望显示默认消息,以防找不到父级。例如:
System.out.println("parent of child process: "
+ p.toHandle().parent().map(ProcessHandle::pid)
.map(Object::toString)
.orElse("not available"));
或者,您可以使用当前进程的pid作为默认值,或者您可以想出的任何其他方法。或者,如果父母的身份对您的工作是绝对必要的,您可能会抛出不同类型的例外。有关优雅使用它的各种方法,请参阅Optional
文档。
没有办法扩展底层进程 - 您可以使用在执行您传递的命令后休眠的 shell 脚本,但我发现这个解决方案充其量是笨拙的。