Process.waitFor: примеры (JAVA)
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 в отдельных нитях и ожидание завершения.
// Advanced 1: чтение потоков, затем waitFor()
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 с обратной проверкой.
// Advanced 2: таймаут и принудительное завершение
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 для неблокирующего уведомления.
// Advanced 3: onExit()
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.
// Advanced 4: параллельный запуск и ожидание
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-подобного шаблона.
// Advanced 5: capture output and exit code
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
Пояснение: простой шаблон чтения однострочного вывода. Для больших потоков читать в потоковых нитях.