在 Runtime.exec 启动一个新线程以启动一个进程的时间和当您告诉该进程销毁自己的时间之间存在竞争条件。
我在一台 linux 机器上,所以我将使用 UNIXProcess.class 文件来说明。
Runtime.exec(...)
将创建一个新实例ProcessBuilder
并启动它,它在 unix 机器上创建一个新UNIXProcess
实例。在构造函数中UNIXProcess
有这段代码,它实际上在后台(分叉)线程中执行进程:
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
Thread t = new Thread("process reaper") {
public void run() {
try {
pid = forkAndExec(prog,
argBlock, argc,
envBlock, envc,
dir,
redirectErrorStream,
stdin_fd, stdout_fd, stderr_fd);
} catch (IOException e) {
gate.setException(e); /*remember to rethrow later*/
gate.exit();
return;
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
stdin_stream = new BufferedOutputStream(new
FileOutputStream(stdin_fd));
stdout_stream = new BufferedInputStream(new
FileInputStream(stdout_fd));
stderr_stream = new FileInputStream(stderr_fd);
return null;
}
});
gate.exit(); /* exit from constructor */
int res = waitForProcessExit(pid);
synchronized (UNIXProcess.this) {
hasExited = true;
exitcode = res;
UNIXProcess.this.notifyAll();
}
}
};
t.setDaemon(true);
t.start();
return null;
}
});
请注意,后台线程设置pid
了 UNIX 进程 ID 字段。这将用于destroy()
告诉操作系统要杀死哪个进程。
因为没有办法确保这个后台线程在destroy()
被调用时已经运行,所以我们可能会在它运行之前尝试杀死进程,或者我们可能会在设置 pid 字段之前尝试杀死进程;pid 未初始化,因此为 0。所以我认为过早调用 destroy 将相当于kill -9 0
UNIXProcessdestroy()
中甚至有一条评论暗示了这一点,但只考虑在进程完成后调用destroy,而不是在它开始之前:
// There is a risk that pid will be recycled, causing us to
// kill the wrong process! So we only terminate processes
// that appear to still be running. Even with this check,
// there is an unavoidable race condition here, but the window
// is very small, and OSes try hard to not recycle pids too
// soon, so this is quite safe.
pid 字段甚至没有被标记为 volatile,所以我们甚至可能一直看不到最新的值。