«1. Введение

В этом уроке мы подробно рассмотрим Process API.

Для более подробного изучения того, как использовать Process для выполнения команды оболочки, мы можем обратиться к нашему предыдущему руководству здесь.

Процесс, на который он ссылается, является выполняющимся приложением. Класс Process предоставляет методы для взаимодействия с этими процессами, включая извлечение вывода, выполнение ввода, мониторинг жизненного цикла, проверку состояния выхода и его уничтожение (уничтожение).

2. Использование класса процесса для компиляции и запуска Java-программы

Давайте рассмотрим пример компиляции и запуска другой Java-программы с помощью Process API:

@Test
public void whenExecutedFromAnotherProgram_thenSourceProgramOutput3() throws IOException {
 
    Process process = Runtime.getRuntime()
      .exec("javac -cp src src\\main\\java\\com\\baeldung\\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-приложение может вызывать любое приложение, работающее в нашей компьютерной системе, с учетом ограничений операционной системы.

Поэтому мы можем запускать приложения. Давайте посмотрим, какие различные варианты использования мы можем запустить, используя Process API.

Класс ProcessBuilder позволяет нам создавать подпроцессы внутри нашего приложения.

Давайте посмотрим демонстрацию открытия приложения Notepad для Windows:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();

4. Уничтожение процесса

Процесс также предоставляет нам методы для уничтожения подпроцессов или процесса. Хотя способ уничтожения приложения зависит от платформы.

Давайте рассмотрим различные варианты использования, которые возможны.

4.1. Уничтожение процесса по ссылке

Допустим, мы используем ОС Windows и хотим создать приложение «Блокнот» и уничтожить его.

Как и прежде, мы можем создать экземпляр приложения Notepad, используя класс ProcessBuilder и метод start().

Затем мы можем вызвать метод destroy() для нашего объекта Process.

4.2. Уничтожение процесса по идентификатору

Мы также можем уничтожать процессы, работающие в нашей операционной системе, которые могут быть созданы не нашим приложением.

При этом следует соблюдать осторожность, так как мы можем неосознанно уничтожить критический процесс, который может сделать операционную систему нестабильной.

Сначала нам нужно узнать идентификатор текущего запущенного процесса, проверив диспетчер задач и узнать pid.

Рассмотрим пример:

long pid = /* PID to kill */;
Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(pid);
optionalProcessHandle.ifPresent(processHandle -> processHandle.destroy());

4.3. Принудительное уничтожение процесса

При выполнении метода destroy() подпроцесс будет уничтожен, как мы видели ранее в статье.

В случае, если destroy() не работает, у нас есть вариант destroyForcably().

Мы всегда должны начинать с метода destroy(). После этого мы можем выполнить быструю проверку подпроцесса, выполнив isAlive().

Если он вернет true, тогда выполните destroyForcily():

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(long timeOut, TimeUnit time)

Когда этот метод выполняется, он помещает текущий поток процесса выполнения в состояние ожидания блокировки, если только подпроцесс не будет завершен или закончится время.

Давайте посмотрим на пример:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertFalse(process.waitFor(1, TimeUnit.SECONDS));

Из приведенного выше примера видно, что текущий поток для продолжения выполнения будет продолжать ждать завершения потока подпроцесса или если истек указанный интервал времени. .

Когда этот метод выполняется, он возвращает логическое значение true, если подпроцесс завершился, или логическое значение false, если время ожидания истекло до выхода подпроцесса.

6. выходное значение()

«Когда этот метод запущен, текущий поток не будет ждать завершения или уничтожения подпроцесса, однако он вызовет исключение 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. Обработка потоков процессов

По умолчанию созданный подпроцесс не имеет своего терминала или консоли. Все его стандартные операции ввода-вывода (например, 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\\baeldung\\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\\baeldung\\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. getOutputStream()

Мы можем отправить входные данные в подпроцесс из родительского процесса:

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. Заключение

Процесс — это мощный класс для взаимодействия на уровне операционной системы. Запуск команд терминала, а также запуск, мониторинг и уничтожение приложений.

Чтобы узнать больше об API процесса Java 9, ознакомьтесь с нашей статьей здесь.

Как всегда, вы найдете исходники на Github.