Process API 指南-Java快速进阶教程
1. 简介
在本教程中,我们将深入了解进程API。
它所指的进程是一个正在执行的应用程序。Process类提供与这些进程交互的方法,包括提取输出、执行输入、监视生命周期、检查退出状态以及销毁(终止)它。
2. 使用进程类编译和运行 Java 程序
让我们看一个在ProcessAPI的帮助下编译和运行另一个Java程序的示例:
@Test
public void whenExecutedFromAnotherProgram_thenSourceProgramOutput3() throws IOException {
Process process = Runtime.getRuntime()
.exec("javac -cp src src\\main\\java\\com\\java9\\process\\OutputStreamExample.java");
process = Runtime.getRuntime()
.exec("java -cp src/main/java com.baeldung.java9.process.OutputStreamExample");
BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
int value = Integer.parseInt(output.readLine());
assertEquals(3, value);
}
因此,在现有Java代码中执行Java代码的应用几乎是无限的。
3. 创建进程
我们的 Java 应用程序可以调用在我们的计算机系统中运行的任何应用程序,这些应用程序受操作系统限制。
因此,我们可以执行应用程序。让我们看看利用进程 API 可以运行的不同用例是什么。
ProcessBuilder类允许我们在应用程序中创建子进程。
让我们看一个打开基于Windows的记事本应用程序的演示:
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
4. 销毁进程
进程还为我们提供了销毁子进程或进程的方法。虽然,应用程序如何被杀死取决于平台。
让我们看看可能的不同用例。
4.1. 通过引用销毁进程
假设我们正在使用 Windows 操作系统并希望生成记事本应用程序并销毁它。
和以前一样,我们可以使用ProcessBuilder类和start() 方法创建记事本应用程序的实例。
然后我们可以在Process对象上调用destroy() 方法。
4.2. 按 ID 销毁进程
我们还可以终止在我们的操作系统中运行的进程,这些进程可能不是由我们的应用程序创建的。
执行此操作时应谨慎,因为我们可能会在不知不觉中破坏可能使操作系统不稳定的关键进程。
我们首先需要通过检查任务管理器来找出当前正在运行的进程的进程ID,并找出pid。
让我们看一个例子:
long pid = /* PID to kill */;
Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(pid);
optionalProcessHandle.ifPresent(processHandle -> processHandle.destroy());
4.3.用武力破坏进程
在执行destroy() 方法时,子进程将被杀死,正如我们在本文前面看到的那样。
在destroy() 不起作用的情况下,我们可以选择强制销毁()。
我们应该总是首先从destroy() 方法开始。之后,我们可以通过执行isAlive() 对子进程执行快速检查。
如果它返回 true,则执行destroyForcibly():
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
process.destroy();
if (process.isAlive()) {
process.destroyForcibly();
}
5. 等待进程完成
我们还有两个重载方法,通过它们我们可以确保我们可以等待一个过程的完成。
5.1.waitFor()
执行此方法时,它将当前执行进程线程置于阻塞等待状态,除非子进程终止。
让我们看一下这个例子:
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertThat(process.waitFor() >= 0);
从上面的例子中我们可以看到,当前线程继续执行,它将继续等待子进程线程结束。子进程结束后,当前线程将继续执行。
5.2.waitFor(1, TimeUnit.SECONDS)
执行此方法时,它将当前执行进程线程置于阻塞等待状态,除非子进程终止或时间不足。
让我们看一下这个例子:
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertFalse(process.waitFor(1, TimeUnit.SECONDS));
我们可以从上面的例子中看到,当前线程继续执行,它将继续等待子进程线程结束,或者是否经过了指定的时间间隔。
执行此方法时,如果子进程已退出,它将返回布尔值 true;如果子进程退出之前已经过等待时间,则返回布尔值 false。
6.exitValue()
运行此方法时,当前线程不会等待子进程终止或销毁,但是,如果子进程未终止,它将引发IllegalThreadStateException。
另一种解决方法是,如果子进程已成功终止,则会导致进程的退出值。
它可以是任何可能的正整数。
让我们看一个示例,当子进程成功终止时,exitValue() 方法返回一个正整数:
@Test
public void
givenSubProcess_whenCurrentThreadWillNotWaitIndefinitelyforSubProcessToEnd_thenProcessExitValueReturnsGrt0()
throws IOException {
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertThat(process.exitValue() >= 0);
}
7.isAlive()
当我们想要执行业务处理时,无论进程是否活跃,这都是主观的。
我们可以执行快速检查以查找返回布尔值的进程是否处于活动状态。
让我们看一个简单的例子:
ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
Thread.sleep(10000);
process.destroy();
assertTrue(process.isAlive());
8. 处理进程流
默认情况下,创建的子进程没有其终端或控制台。其所有标准 I/O(即 stdin、stdout、stderr)操作都将发送到父进程。因此,父进程可以使用这些流向子进程提供输入并从子进程获取输出。
因此,这为我们提供了极大的灵活性,因为它使我们能够控制子进程的输入/输出。
8.1.getErrorStream()
有趣的是,我们可以获取从子进程生成的错误,然后执行业务处理。
之后,我们可以根据我们的要求执行特定的业务进程检查。
让我们看一个例子:
@Test
public void givenSubProcess_whenEncounterError_thenErrorStreamNotNull() throws IOException {
Process process = Runtime.getRuntime().exec(
"javac -cp src src\\main\\java\\com\\java9\\process\\ProcessCompilationError.java");
BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String errorString = error.readLine();
assertNotNull(errorString);
}
8.2.getInputStream()
我们还可以获取子进程生成的输出并在父进程中使用,从而允许在进程之间共享信息:
@Test
public void givenSourceProgram_whenReadingInputStream_thenFirstLineEquals3() throws IOException {
Process process = Runtime.getRuntime().exec(
"javac -cp src src\\main\\java\\com\\java9\\process\\OutputStreamExample.java");
process = Runtime.getRuntime()
.exec("java -cp src/main/java com.baeldung.java9.process.OutputStreamExample");
BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
int value = Integer.parseInt(output.readLine());
assertEquals(3, value);
}
8.3.ge tOutputStream()
我们可以从父进程向子进程发送输入:
Writer w = new OutputStreamWriter(process.getOutputStream(), "UTF-8");
w.write("send to child\n");
8.4. 过滤进程流
这是与选择性运行进程交互的完全有效的用例。
Process为我们提供了根据某个谓词有选择地过滤正在运行的进程的工具。
之后,我们可以在此选择性进程集上执行业务操作:
@Test
public void givenRunningProcesses_whenFilterOnProcessIdRange_thenGetSelectedProcessPid() {
assertThat(((int) ProcessHandle.allProcesses()
.filter(ph -> (ph.pid() > 10000 && ph.pid() < 50000))
.count()) > 0);
}
9. 结论
进程是用于操作系统级别交互的强大类。触发终端命令以及启动、监控和终止应用程序。