Process.waitFor: примеры (JAVA)

Подробный разбор метода Process.waitFor в Java
Раздел: Работа с процессами и памятью (Runtime, Process)
Process.waitFor: int

Описание метода Process.waitFor

Метод Process.waitFor применяется для ожидания завершения внешнего процесса, запущенного из Java. Существует две перегрузки:

  • public int waitFor() throws InterruptedException - блокирует текущий поток до завершения процесса и возвращает код завершения (exit code).
  • public boolean waitFor(long timeout, java.util.concurrent.TimeUnit unit) throws InterruptedException - ожидает не более заданного времени и возвращает true, если процесс завершился до истечения таймаута, и false, если время вышло; сам процесс при этом продолжает выполняться, если его не остановить вручную.

Особенности поведения:

  • Возвращаемое значение у первой перегрузки - целое число, код завершения процесса. По соглашению ноль обычно означает успешное выполнение, ненулевые значения - ошибки, но конкретный смысл кода зависит от вызываемого процесса и операционной системы.
  • Вторая перегрузка предоставляет неблокирующее ожидание с таймаутом и не изменяет состояние процесса при таймауте, она только сообщает, завершился ли процесс в отведенное время.
  • Если текущий поток прерывается во время ожидания, выбрасывается InterruptedException. Процесс при этом продолжает выполняться, если его явно не завершить.
  • Метод блокирует вызывающий поток. Для асинхронной реакции можно использовать другие механизмы, например ProcessHandle.onExit() с CompletableFuture (в Java 9 и выше).
  • Чтение потоков вывода и ошибок внешнего процесса следует организовать отдельно. Отсутствие чтения больших объемов данных из stdout/stderr может привести к блокировке самого процесса и, как следствие, к бесконечному ожиданию в waitFor.

Типичные методы, связанные с ожиданием и состоянием процесса:

  • int exitValue() - возвращает код завершения, но если процесс ещё не завершен, выбрасывает IllegalThreadStateException.
  • boolean isAlive() - проверяет, выполняется ли процесс в данный момент.
  • Process.destroy() и Process.destroyForcibly() - методы для завершения процесса.

Короткие примеры использования

1) Простое ожидание и получение кода выхода.

// Пример 1: waitFor()
Process p = new ProcessBuilder("sh", "-c", "echo hello; exit 0").start();
int code = p.waitFor();
System.out.println("exit=" + code);
exit=0

2) Ожидание с таймаутом: завершился вовремя.

// Пример 2: waitFor(timeout) успешное завершение
Process p = new ProcessBuilder("sh", "-c", "sleep 1; exit 2").start();
boolean finished = p.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("finished=" + finished + ", exit=" + (finished ? p.exitValue() : -1));
finished=true, exit=2

3) Ожидание с таймаутом: таймаут истек.

// Пример 3: waitFor(timeout) таймаут
Process p = new ProcessBuilder("sh", "-c", "sleep 10; exit 0").start();
boolean finished = p.waitFor(1, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("finished=" + finished);
if (!finished) {
    p.destroyForcibly();
    System.out.println("killed");
}
finished=false
killed

4) Пример с прерываниями: InterruptedException при прерывании потока.

// Пример 4: прерывание ожидания
Thread t = new Thread(() -> {
    try {
        Process p = new ProcessBuilder("sh", "-c", "sleep 5").start();
        p.waitFor();
        System.out.println("done");
    } catch (InterruptedException e) {
        System.out.println("interrupted");
    } catch (IOException e) {
        e.printStackTrace();
    }
});

t.start();
Thread.sleep(1000);
t.interrupt();
interrupted

Похожие механизмы в Java и их отличия

  • ProcessHandle.onExit() (Java 9+) - возвращает CompletableFuture<ProcessHandle>, позволяет подписаться на событие завершения процесса без блокировки потока. Подходит, когда нужен неблокирующий подход и композиция асинхронных действий.
  • Process.isAlive() + exitValue() - можно опрашивать состояние и брать код завершения, но вызов exitValue() при живом процессе приводит к IllegalThreadStateException. Используется для ненадежного или периодического опроса.
  • Комбинация чтения stdout/stderr в отдельных потоках и последующий waitFor() - стандартный шаблон для предотвращения блокировок, когда процесс генерирует много вывода.

Выбор зависит от задачи: для простого синхронного запуска подойдёт waitFor(). Для реактивного или неблокирующего поведения лучше ProcessHandle.onExit(). Для контроля времени выполнения - waitFor(timeout, unit) в сочетании с destroyForcibly().

Эквиваленты в других языках и отличия

PHP

// PHP пример (proc_open)
$proc = proc_open('sleep 2; exit 5', [], $pipes);
$status = proc_get_status($proc);
$exit = proc_close($proc);
echo "exit=$exit\n";
exit=5

Отличие: proc_close блокирует до завершения процесса и возвращает код выхода. Для асинхронности нужно использовать неблокирующие дескрипторы или расширения.

JavaScript (Node.js)

// Node.js пример
const { spawn } = require('child_process');
const p = spawn('sh', ['-c', 'sleep 1; exit 3']);
p.on('exit', (code) => console.log('exit='+code));
exit=3

Отличие: Node.js использует события и колбэки, ожидание неблокирующее по умолчанию.

Python

# Python пример
import subprocess
p = subprocess.Popen(['sh', '-c', 'sleep 1; exit 7'])
code = p.wait()
print('exit=', code)
exit= 7

Отличие: в Python есть wait(timeout) с таймаутом (в Python 3) и communicate() для безопасного чтения вывода.

C#

// C# пример
using System.Diagnostics;
var p = Process.Start(new ProcessStartInfo("bash", "-c 'exit 4'") { RedirectStandardOutput = true });
p.WaitForExit();
Console.WriteLine("exit=" + p.ExitCode);
exit=4

Отличие: в .NET есть блокирующий WaitForExit() и перегрузки с таймаутом, а также событие Exited.

Go

// Go пример
package main
import (
    "os/exec"
    "fmt"
)
func main(){
    cmd := exec.Command("sh", "-c", "exit 6")
    err := cmd.Run()
    if exitErr, ok := err.(*exec.ExitError); ok {
        fmt.Println("exit=", exitErr.ExitCode())
    } else {
        fmt.Println("exit=0")
    }
}
exit= 6

Отличие: в Go метод Run блокирует, а для асинхронности используются goroutine и Wait.

Kotlin

// Kotlin пример ( JVM )
val p = ProcessBuilder("sh", "-c", "exit 8").start()
val code = p.waitFor()
println("exit=$code")
exit=8

Отличие: в Kotlin на JVM используется тот же API, что и в Java.

SQL

В SQL нет прямого аналога управления ОС-процессами. В некоторых СУБД есть встроенные расширения или внешние процедуры, но они зависят от платформы и обычно не предназначены для управления системными процессами.

Типичные ошибки при использовании

1) Блокировка из-за непотребления stdout/stderr.

// Неправильный пример: большой вывод блокирует процесс
Process p = new ProcessBuilder("sh", "-c", "yes | head -n 100000").start();
int code = p.waitFor();
System.out.println(code);
[программа зависает или долго выполняется, поскольку буфер stdout переполняется]

Решение: читать stdout и stderr в отдельной нити или перенаправлять вывод.

2) Использование exitValue() для проверки статуса без предварительного ожидания.

// Ошибка: выбрасывается IllegalThreadStateException
Process p = new ProcessBuilder("sh", "-c", "sleep 5").start();
int v = p.exitValue(); // если процесс еще жив - исключение
Exception in thread "main" java.lang.IllegalThreadStateException

3) Игнорирование InterruptedException: пропуск обработки приводит к потере информации о прерывании.

// Плохая обработка
try {
    p.waitFor();
} catch (InterruptedException e) {
    // пусто
}
[потеря статуса прерывания, программа может продолжить работать некорректно]

4) Блокировка UI-потока мобильного или десктопного приложения из-за вызова waitFor в главном потоке.

[интерфейс перестает отвечать]

Изменения и развитие API в последних версиях

  • Java 8 добавила перегрузку waitFor(long timeout, TimeUnit unit), предоставив удобный таймаутный механизм.
  • Java 9 представила ProcessHandle и ProcessHandle.onExit(), что дало неблокирующую альтернативу наблюдения за завершением процесса через CompletableFuture.
  • В последующих версиях API управления процессами дополнительно развивался через ProcessHandle (методы для получения информации о процессе и иерархии), но базовая семантика waitFor осталась прежней.

Для большинства задач в современных версиях Java рекомендуется сочетать проверенные методы чтения потоков с неблокирующими инструментами ProcessHandle, если нужен асинхронный подход.

Расширенные и нетривиальные примеры

1) Безопасное чтение stdout и stderr в отдельных нитях и ожидание завершения.

Пример java
// Advanced 1: чтение потоков, затем waitFor()
Пример java
ProcessBuilder pb = new ProcessBuilder("sh", "-c", "for i in $(seq 1 1000); do echo line$i; done");
Process p = pb.start();

Thread out = new Thread(() -> {
    try (var in = p.getInputStream(); var r = new java.io.BufferedReader(new java.io.InputStreamReader(in))) {
        String s; while ((s = r.readLine()) != null) { /* можно буферизовать */ }
    } catch (IOException e) { e.printStackTrace(); }
});
Thread err = new Thread(() -> {
    try (var in = p.getErrorStream(); var r = new java.io.BufferedReader(new java.io.InputStreamReader(in))) {
        String s; while ((s = r.readLine()) != null) { /* обработка ошибок */ }
    } catch (IOException e) { e.printStackTrace(); }
});
out.start(); err.start();
int exit = p.waitFor();
out.join(); err.join();
System.out.println("exit=" + exit);
exit=0

Пояснение: чтение потоков в отдельных нитях предотвращает блокировки внутреннего буфера операционной системы.

2) Комбинация waitFor(timeout) и destroyForcibly с обратной проверкой.

Пример java
// Advanced 2: таймаут и принудительное завершение
Пример java
Process p = new ProcessBuilder("sh", "-c", "sleep 30").start();
if (!p.waitFor(2, java.util.concurrent.TimeUnit.SECONDS)) {
    p.destroy();
    if (!p.waitFor(1, java.util.concurrent.TimeUnit.SECONDS)) {
        p.destroyForcibly();
        p.waitFor();
    }
}
System.out.println("finished, exit=" + p.exitValue());
finished, exit=143

Пояснение: сначала дается шанс корректно завершить процесс, затем принудительное завершение. Код выхода зависит от ОС.

3) Использование ProcessHandle.onExit для неблокирующего уведомления.

Пример java
// Advanced 3: onExit()
Пример java
Process p = new ProcessBuilder("sh", "-c", "sleep 1; exit 9").start();
p.onExit().thenAccept(ph -> System.out.println("exit=" + ph.exitValue()));
// основной поток не блокируется, можно выполнять другие задачи
Thread.sleep(2000);
exit=9

4) Запуск множества процессов и ожидание всех с помощью ExecutorService и Future.

Пример java
// Advanced 4: параллельный запуск и ожидание
Пример java
var commands = java.util.List.of(
    new String[]{"sh", "-c", "sleep 1; exit 1"},
    new String[]{"sh", "-c", "sleep 2; exit 2"}
);
var ex = java.util.concurrent.Executors.newFixedThreadPool(2);
var futures = new java.util.ArrayList>();
for (var cmd : commands) {
    futures.add(ex.submit(() -> {
        Process p = new ProcessBuilder(cmd).start();
        try { return p.waitFor(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return -1; }
    }));
}
for (var f : futures) System.out.println("exit=" + f.get());
ex.shutdown();
exit=1
exit=2

Пояснение: используется пул потоков для одновременного запуска и ожидания множества процессов без блокировки одного потока на всех.

5) Сценарий: получение вывода и кода завершения безопасно с помощью communicate-подобного шаблона.

Пример java
// Advanced 5: capture output and exit code
Пример java
Process p = new ProcessBuilder("sh", "-c", "echo out; echo err 1>&2; exit 11").start();
var outReader = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
var errReader = new java.io.BufferedReader(new java.io.InputStreamReader(p.getErrorStream()));
String out = outReader.readLine();
String err = errReader.readLine();
int code = p.waitFor();
System.out.println("out='" + out + "' err='" + err + "' exit=" + code);
out='out' err='err' exit=11

Пояснение: простой шаблон чтения однострочного вывода. Для больших потоков читать в потоковых нитях.

джава Process.waitFor function comments

En
Process.waitFor Causes the current thread to wait for the process to terminate