使用 Java 的 Runtime.exec() 时如何添加超时值?

Posted

技术标签:

【中文标题】使用 Java 的 Runtime.exec() 时如何添加超时值?【英文标题】:How to add a timeout value when using Java's Runtime.exec()? 【发布时间】:2010-10-22 22:30:15 【问题描述】:

我有一种方法用于在本地主机上执行命令。我想为该方法添加一个超时参数,以便如果被调用的命令没有在合理的时间内完成,该方法将返回一个错误代码。这是到目前为止的样子,没有超时功能:

public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError)
    throws IOException, InterruptedException

    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(commandLine);

    if (printOutput)
    
        BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        System.out.println("Output:  " + outputReader.readLine());
    

    if (printError)
    
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        System.out.println("Error:  " + errorReader.readLine());
    

    return process.waitFor();

谁能建议我实现超时参数的好方法?

【问题讨论】:

我认为这是一个简化的例子。您知道如果标准输出或标准错误的缓冲区已满,这将阻塞。您已经异步读取它们,两者都是并行的。 @MichaelPiefel 是的。所以我正在为这些启动一个新线程,但问题是当进程死亡时,是否也必须杀死线程? 【参考方案1】:

如果您使用的是 Java 8 或更高版本,您可以简单地使用新的 waitFor with timeout:

Process p = ...
if(!p.waitFor(1, TimeUnit.MINUTES)) 
    //timeout - kill the process. 
    p.destroy(); // consider using destroyForcibly instead

【讨论】:

【参考方案2】:
public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError,
                                     final long timeout)
      throws IOException, InterruptedException, TimeoutException 
  Runtime runtime = Runtime.getRuntime();
  Process process = runtime.exec(commandLine);
  /* Set up process I/O. */
  ... 
  Worker worker = new Worker(process);
  worker.start();
  try 
    worker.join(timeout);
    if (worker.exit != null)
      return worker.exit;
    else
      throw new TimeoutException();
   catch(InterruptedException ex) 
    worker.interrupt();
    Thread.currentThread().interrupt();
    throw ex;
   finally 
    process.destroyForcibly();
  


private static class Worker extends Thread 
  private final Process process;
  private Integer exit;
  private Worker(Process process) 
    this.process = process;
  
  public void run() 
    try  
      exit = process.waitFor();
     catch (InterruptedException ignore) 
      return;
    
    

【讨论】:

我试过这个,但我得到的 worker.exit 值始终为空。 ***.com/questions/23756326/… 不要只是摆出一段代码。请提供解释。 也许我不明白Thread.join(long time)Thread.wait(long time)Thread.join(long time) 可能会调用。如果超时已过期,这将如何停止进程执行?我已经使用上面的方法运行了一个测试,其中我将当前时间打印到stdout,运行该过程,然后再次打印时间。该过程大约需要 3 秒来执行。该过程总是在超时期限过去后完成,即使我已将超时指定为 1 秒并希望得到 TimeoutException @AgiHammerthief 我依靠Process.destroy() 在超时后杀死未完成的进程。我会做一些测试。你在运行什么操作系统?您是在 IDE 中还是从 shell 中运行测试?【参考方案3】:

按照answer by erickson,我创建了一种更通用的方法来做同样的事情。

public class ProcessWithTimeout extends Thread

    private Process m_process;
    private int m_exitCode = Integer.MIN_VALUE;

    public ProcessWithTimeout(Process p_process)
    
        m_process = p_process;
    

    public int waitForProcess(int p_timeoutMilliseconds)
    
        this.start();

        try
        
            this.join(p_timeoutMilliseconds);
        
        catch (InterruptedException e)
        
            this.interrupt();
        

        return m_exitCode;
    

    @Override
    public void run()
    
        try
         
            m_exitCode = m_process.waitFor();
        
        catch (InterruptedException ignore)
        
            // Do nothing
        
        catch (Exception ex)
        
            // Unexpected exception
        
    

现在,你要做的就是:

Process process = Runtime.getRuntime().exec("<your command goes here>");
ProcessWithTimeout processWithTimeout = new ProcessWithTimeout(process);
int exitCode = processWithTimeout.waitForProcess(5000);

if (exitCode == Integer.MIN_VALUE)

    // Timeout

else

    // No timeout !

【讨论】:

非常有用。我用过这个模具(因为它让我的流消耗线程保持原样)。但是,我已经更新了waitForProcess 以解决虚假中断...:paste.ubuntu.com/9898052 我会将p_timeoutMilliseconds 更改为长。例如public int waitForProcess(long timeoutMilliseconds) 我认为使用 Interger.MIN_VALUE 来表示超时并不好【参考方案4】:

我使用附带详细代码示例的建议的三种方法来实现这一点(我是线程编程的新手,这些示例代码非常宝贵——如果只是这样做的话,我仍然会摸不着头脑英文解释,无代码)。

我使用三种方法实现了我正在使用的实用程序类,这些方法用于执行具有超时的命令,如下所示:

package com.abc.network.lifecycle.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Utility class for performing process related functions such as command line processing.
 */
public class ProcessUtility


    static Log log = LogFactory.getLog(ProcessUtility.class);

    /**
     * Thread class to be used as a worker
     */
    private static class Worker
        extends Thread
    
        private final Process process;
        private Integer exitValue;

        Worker(final Process process)
        
            this.process = process;
        

        public Integer getExitValue()
        
            return exitValue;
        

        @Override
        public void run()
        
            try
            
                exitValue = process.waitFor();
            
            catch (InterruptedException ignore)
            
                return;
            
        
    

    /**
     * Executes a command.
     * 
     * @param command
     * @param printOutput
     * @param printError
     * @param timeOut
     * @return
     * @throws java.io.IOException
     * @throws java.lang.InterruptedException
     */
    public static int executeCommandWithExecutors(final String command,
                                                  final boolean printOutput,
                                                  final boolean printError,
                                                  final long timeOut)
    
        // validate the system and command line and get a system-appropriate command line 
        String massagedCommand = validateSystemAndMassageCommand(command);

        try
        
            // create the process which will run the command
            Runtime runtime = Runtime.getRuntime();
            final Process process = runtime.exec(massagedCommand);

            // consume and display the error and output streams
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
            outputGobbler.start();
            errorGobbler.start();

            // create a Callable for the command's Process which can be called by an Executor 
            Callable<Integer> call = new Callable<Integer>()
            
                public Integer call()
                    throws Exception
                
                    process.waitFor();
                    return process.exitValue();
                
            ;

            // submit the command's call and get the result from a 
            Future<Integer> futureResultOfCall = Executors.newSingleThreadExecutor().submit(call);
            try
            
                int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS);
                return exitValue;
            
            catch (TimeoutException ex)
            
                String errorMessage = "The command [" + command + "] timed out.";
                log.error(errorMessage, ex);
                throw new RuntimeException(errorMessage, ex);
            
            catch (ExecutionException ex)
            
                String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
                log.error(errorMessage, ex);
                throw new RuntimeException(errorMessage, ex);
            
        
        catch (InterruptedException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
        catch (IOException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
    

    /**
     * Executes a command.
     * 
     * @param command
     * @param printOutput
     * @param printError
     * @param timeOut
     * @return
     * @throws java.io.IOException
     * @throws java.lang.InterruptedException
     */
    public static int executeCommandWithSleep(final String command,
                                              final boolean printOutput,
                                              final boolean printError,
                                              final long timeOut)
    
        // validate the system and command line and get a system-appropriate command line 
        String massagedCommand = validateSystemAndMassageCommand(command);

        try
        
            // create the process which will run the command
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(massagedCommand);

            // consume and display the error and output streams
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
            outputGobbler.start();
            errorGobbler.start();

            // run a thread which will set a flag once it has slept for the timeout period
            final boolean[] flags =  true ;
            new Thread()
            
                @Override
                public void run()
                
                    try
                    
                        Thread.sleep(timeOut);
                    
                    catch (InterruptedException ex)
                    
                        String errorMessage = "Timeout loop thread unexpectedly interrupted.";
                        log.error(errorMessage, ex);
                        throw new RuntimeException(errorMessage, ex);
                    
                    flags[0] = false;
                
            .start();

            // execute the command and wait 
            int returnValue = -1;
            while (flags[0] && (returnValue < 0))
            
                returnValue = process.waitFor();
            

            // if the command timed out then log it
            if (returnValue < 0)
            
                log.warn("The command [" + command + "] did not complete before the timeout period expired (timeout: " +
                         timeOut + " ms)");
            

            return returnValue;
        
        catch (InterruptedException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
        catch (IOException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
    

    /**
     * Executes a command.
     * 
     * @param command
     * @param printOutput
     * @param printError
     * @param timeOut
     * @return
     * @throws java.io.IOException
     * @throws java.lang.InterruptedException
     */
    public static int executeCommandWithWorker(final String command,
                                               final boolean printOutput,
                                               final boolean printError,
                                               final long timeOut)
    
        // validate the system and command line and get a system-appropriate command line 
        String massagedCommand = validateSystemAndMassageCommand(command);

        try
        
            // create the process which will run the command
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(massagedCommand);

            // consume and display the error and output streams
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
            outputGobbler.start();
            errorGobbler.start();

            // create and start a Worker thread which this thread will join for the timeout period 
            Worker worker = new Worker(process);
            worker.start();
            try
            
                worker.join(timeOut);
                Integer exitValue = worker.getExitValue();
                if (exitValue != null)
                
                    // the worker thread completed within the timeout period
                    return exitValue;
                

                // if we get this far then we never got an exit value from the worker thread as a result of a timeout 
                String errorMessage = "The command [" + command + "] timed out.";
                log.error(errorMessage);
                throw new RuntimeException(errorMessage);
            
            catch (InterruptedException ex)
            
                worker.interrupt();
                Thread.currentThread().interrupt();
                throw ex;
            
        
        catch (InterruptedException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
        catch (IOException ex)
        
            String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
            log.error(errorMessage, ex);
            throw new RuntimeException(errorMessage, ex);
        
    

    /**
     * Validates that the system is running a supported OS and returns a system-appropriate command line.
     * 
     * @param originalCommand
     * @return
     */
    private static String validateSystemAndMassageCommand(final String originalCommand)
    
        // make sure that we have a command
        if (originalCommand.isEmpty() || (originalCommand.length() < 1))
        
            String errorMessage = "Missing or empty command line parameter.";
            log.error(errorMessage);
            throw new RuntimeException(errorMessage);
        

        // make sure that we are running on a supported system, and if so set the command line appropriately
        String massagedCommand;
        String osName = System.getProperty("os.name");
        if (osName.equals("Windows XP"))
        
            massagedCommand = "cmd.exe /C " + originalCommand;
        
        else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux"))
        
            massagedCommand = originalCommand;
        
        else
        
            String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" +
                                  osName + "\').";
            log.error(errorMessage);
            throw new RuntimeException(errorMessage);
        

        return massagedCommand;
    

我创建了一个类来使用和显示命令的输出和错误流(取自http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4):

package com.abc.network.lifecycle.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Utility thread class which consumes and displays stream input.
 * 
 * Original code taken from http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
 */
class StreamGobbler
    extends Thread

    static private Log log = LogFactory.getLog(StreamGobbler.class);
    private InputStream inputStream;
    private String streamType;
    private boolean displayStreamOutput;

    /**
     * Constructor.
     * 
     * @param inputStream the InputStream to be consumed
     * @param streamType the stream type (should be OUTPUT or ERROR)
     * @param displayStreamOutput whether or not to display the output of the stream being consumed
     */
    StreamGobbler(final InputStream inputStream,
                  final String streamType,
                  final boolean displayStreamOutput)
    
        this.inputStream = inputStream;
        this.streamType = streamType;
        this.displayStreamOutput = displayStreamOutput;
    

    /**
     * Consumes the output from the input stream and displays the lines consumed if configured to do so.
     */
    @Override
    public void run()
    
        try
        
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null)
            
                if (displayStreamOutput)
                
                    System.out.println(streamType + ">" + line);
                
            
        
        catch (IOException ex)
        
            log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
            ex.printStackTrace();
        
    

我创建了一个大约需要 10 秒才能完成的测试命令:

#!/bin/bash
sleep 10
echo 'TEST COMMAND RAN OK'

然后我创建了一个测试程序来测试三种不同的方法,分别以 5 秒的超时值(命令应该失败)和 15 秒的超时值(命令应该成功)调用每个方法:

package com.abc.network.lifecycle.util;

public class ProcessUtilityTester


    /**
     * @param args
     */
    public static void main(final String[] args)
    
        try
        
            String command = args[0];
            int exitValue = -1;
            System.out.println("\n\n5000ms timeout With Executors:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 5000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
            System.out.println("\n\n5000ms timeout With Sleep:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 5000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
            System.out.println("\n\n5000ms timeout With Worker:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 5000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
            System.out.println("\n\n15000ms timeout With Executors:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 15000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
            System.out.println("\n\n15000ms timeout With Sleep:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 15000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
            System.out.println("\n\n15000ms timeout With Worker:");
            try
            
                exitValue = -1;
                exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 15000);
            
            catch (Exception ex)
            
                ex.printStackTrace();
            
            finally
            
                System.out.println("\nExit value:" + exitValue);
            
        
        catch (Exception ex)
        
            ex.printStackTrace();
        
        finally
        
            System.exit(0);
        
    


这是我在运行测试程序时看到的:

5000ms timeout With Executors:
May 1, 2009 1:55:19 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithExecutors
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
        at java.util.concurrent.FutureTask.get(FutureTask.java:91)
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:186)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
Caused by: java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
        at java.util.concurrent.FutureTask.get(FutureTask.java:91)
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
        ... 1 more

Exit value:-1


5000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK

Exit value:0


5000ms timeout With Worker:
May 1, 2009 1:55:34 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithWorker
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
        at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithWorker(ProcessUtility.java:338)
        at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:47)

Exit value:-1


15000ms timeout With Executors:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK

Exit value:0


15000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK

Exit value:0


15000ms timeout With Worker:
OUTPUT>TEST COMMAND RAN OK

Exit value:0

因此,据我所知,使用 Worker 线程类的方法效果最好,因为它在两种情况下都给出了预期的结果。使用 Executors 的方法也可以按预期工作,但需要注意的是,它似乎在 15000 毫秒 timout 情况下运行了两次命令(即,我看到了两次命令的输出)。使用 sleep() 方法的方法不会在 5000ms 超时情况下按预期使命令超时,并显示两次输出,但在 15000ms 超时情况下按预期运行命令。

【讨论】:

我认为您不应该在等待进程退出代码时返回中断。这应该循环,直到您成功获得退出代码。 请问为什么使用cmd.exe而不是直接调用进程的exe? 我试过了,但它总是抛出超时异常***.com/questions/23756326/… @JamesAdams 我必须将 Windows 7 添加到操作系统列表中才能运行。奇怪的是,工人为我跑了两次。从 java 运行外部命令对我来说似乎过于混乱。【参考方案5】:

对于使用执行器框架的每个人:你们都忘记关闭执行器。所以改成下面这样:

ExecutorService service = Executors.newSingleThreadExecutor();
try 
    Future<Integer> ft = service.submit(call);
    try 
        int exitVal = ft.get(2000L, TimeUnit.MILLISECONDS);
        return exitVal;
     catch (TimeoutException to) 
        p.destroy();
        throw to;
    

finally 
    service.shutdown();

如果您不这样做,您的程序将保持一个活动的非守护线程,确保您的程序在调用 System.exit 之前永远不会退出

【讨论】:

【参考方案6】:

对于那些不能使用新的 Java 8 方法 waitFor(long timeout, TimeUnit unit)(因为他们在 android 上或者根本无法升级)的人,您可以简单地从 JDK 源代码中将其撕下,并将其添加到您的 utils 文件中的某个位置:

public boolean waitFor(long timeout, TimeUnit unit, final Process process)
            throws InterruptedException
    
        long startTime = System.nanoTime();
        long rem = unit.toNanos(timeout);

        do 
            try 
                process.exitValue();
                return true;
             catch(IllegalThreadStateException ex) 
                if (rem > 0)
                    Thread.sleep(
                            Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
            
            rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
         while (rem > 0);
        return false;
    

我对 JDK8 源代码的原始代码所做的唯一更改是添加了 Process 参数,以便我们可以从进程中调用 exitValue 方法。

如果进程尚未终止,exitValue 方法将直接尝试返回或抛出IllegalThreadStateException。在这种情况下,我们等待收到的超时并终止。

该方法返回一个布尔值,因此如果它返回 false,那么您就知道您需要手动终止该进程。

这种方法似乎比上面发布的任何方法都简单(当然可以直接调用 waitFor)。

【讨论】:

【参考方案7】:

小型应用的轻量级解决方案:

public class Test 
    public static void main(String[] args) throws java.io.IOException, InterruptedException    
        Process process = new ProcessBuilder().command("sleep", "10").start();

        int i=0;
        boolean deadYet = false;
        do 
            Thread.sleep(1000);
            try 
                process.exitValue();
                deadYet = true;
             catch (IllegalThreadStateException e) 
                System.out.println("Not done yet...");
                if (++i >= 5) throw new RuntimeException("timeout");
            
         while (!deadYet);
    

【讨论】:

【参考方案8】:

作为委托实施,如果超过您的阈值才能完成,则调用失败。

【讨论】:

【参考方案9】:

如果有可用的,请尝试在单独的线程或事件队列中使用 Timer(或 Sleep())。

【讨论】:

【参考方案10】:

有多种方法可以做到这一点,但我会考虑使用 Executor——它只是帮助您封装将退出值或异常从线程传递回原始调用者。

    final Process p = ...        
    Callable<Integer> call = new Callable<Integer>() 
    public Integer call() throws Exception 
        p.waitFor();
        return p.exitValue();
      
    ;
    Future<Integer> ft = Executors.newSingleThreadExecutor().submit(call);
    try 
      int exitVal = ft.get(2000L, TimeUnit.MILLISECONDS);
      return exitVal;
     catch (TimeoutException to) 
      p.destroy();
      throw to;
    

我认为您无法绕过等待超时的竞争条件,然后进程在您调用 destroy() 之前终止。

【讨论】:

【参考方案11】:

我还测试了 worker 的实现,并且效果很好。在处理进程io下,我添加了线程来处理stde和stdo。如果工作线程超时,我也会退出 io 线程。

Process p = Runtime.getRuntime().exec(cmd.trim());

            //setup error and output stream threads
            CommandStreamThread eStream = new CommandStreamThread(p.getErrorStream(), "STDE");            
            CommandStreamThread oStream = new CommandStreamThread(p.getInputStream(), "STDO");

            // kick them off
            eStream.start();
            oStream.start();

            //setup a worker thread so we can time it out when we need
            CommandWorkerThread worker=new CommandWorkerThread(p);
            worker.start();

            try 
                worker.join(this.getTimeout());
                if (worker.getExit() != null)
                    return worker.getExit();
                else
                    throw new TimeoutException("Timeout reached:"+this.getTimeout()+" ms");
             catch(InterruptedException ex) 
                eStream.interrupt();
                oStream.interrupt();
                worker.interrupt();
                Thread.currentThread().interrupt();
                throw ex;
             finally 
                p.destroy();
            

【讨论】:

哦,我现在看到你的其他答案了。我认为将所有这些都放在一个答案中会更好。【参考方案12】:

首先是一些背景信息,我遇到了运行命令时超时的问题,因为我尝试执行的程序永远不会打印任何调试或错误信息以防出现错误,并且只会继续在内部自行重试导致进程卡住了,因为在重试时从未出现错误或输出流。

所以在process.exec()process.start() 之后,

它会永远卡在这条线上,

BufferedReader input = new BufferedReader(newInputStreamReader(process.getInputStream()));

根据带有public boolean waitFor(long timeout,TimeUnit unit) 方法的java 1.8,它应该在指定的超时后“理想地”超时,但在我的情况下,由于某种原因它从未超时可能是因为我将应用程序作为Windows 服务运行(我已经检查过用户权限和帐户上的所有内容,但没有帮助)。

所以我尝试使用以下逻辑来实现它,我们将继续使用input.ready() 和超时标志检查输入流。与现有的所有其他解决方案相比,这个简单的解决方案就像一个魅力。

代码:

public boolean runCommand() throws IOException, InterruptedException, Exception 
    StringBuilder rawResponse = new StringBuilder();
    System.out.println("Running Command " + Arrays.toString(command));
    ProcessBuilder processBuilder = new ProcessBuilder(Arrays.asList(command));
    processBuilder.redirectErrorStream(true);
    Process process = processBuilder.start(); //Executing the process
    BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
    waitForTimeout(input, process); //Waiting for Timout
    String line;
    while ((line = input.readLine()) != null) 
        rawResponse.append(line).append("\n");
    
    return true;



//Timeout method 
private void waitForTimeout(BufferedReader input, Process process) throws InterruptedException, Exception 
    int timeout = 5;
    while (timeout > 0) 
        if (!process.isAlive() || input.ready()) 
            break;
         else 
            timeout--;
            Thread.sleep(1000);
            if (timeout == 0 && !input.ready()) 
                destroyProcess(process);
                throw new Exception("Timeout in executing the command "+Arrays.toString(command));
            
        
    

【讨论】:

我在 groovy 中遇到了同样的问题,这个答案最终解决了我的问题。这一切都花了很多时间,我将分享我的最后一个临时构建器课程:gist.github.com/meonlol/df1174d2c1d84e7fe8db8710cd7beff1希望它对某人有所帮助。【参考方案13】:

您可以启动一个线程,该线程在您想要的时间内休眠,并在休眠后更改您在 executeCommandLine 方法中循环的布尔值。

类似的东西(未经测试或编译,此解决方案是一个原型,如果适合您的需要,您应该对其进行重构):

public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError)
    throws IOException, InterruptedException

    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(commandLine);

    if (printOutput)
    
        BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        System.out.println("Output:  " + outputReader.readLine());
    

    if (printError)
    
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        System.out.println("Error:  " + errorReader.readLine());
    

    ret = -1;
    final[] b = true;
    new Thread()
       public void run()
           Thread.sleep(2000); //to adapt
           b[0] = false;
       
    .start();
    while(b[0])
    
          ret = process.waitFor();
    

    return ret;

【讨论】:

将布尔值放入最终数组的原因是什么?有什么需要避免的问题吗? 线程启动是一个内部类并且要访问一个外部变量,那些必须是最终的。由于 boolean 是本机类型而不是对象,因此将其设置在数组中会使其成为对象。在 Process 属性中,您可以看到 Process p 也是最终的,原因相同。【参考方案14】:

这里是 StreamThread

public class CommandStreamThread extends Thread
        private InputStream iStream;
        private String cPrompt;

        CommandStreamThread (InputStream is, String cPrompt)
        
            this.iStream = is;
            this.cPrompt = cPrompt;
        

        public void run()
        
            try
            
                InputStreamReader streamReader= new InputStreamReader(this.iStream);
                BufferedReader reader = new BufferedReader(streamReader);


                String linesep=System.getProperty("line.separator");
                String line=null;
                while ((line=reader.readLine())!=null)
                    System.out.println(line);
                    //Process the next line seperately in case this is EOF is not preceded by EOL
                    int in;
                    char[] buffer=new char[linesep.length()];
                    while ( (in = reader.read(buffer)) != -1)
                        String bufferValue=String.valueOf(buffer, 0, in);
                        System.out.print(bufferValue);
                        if (bufferValue.equalsIgnoreCase(linesep))
                            break;
                    
                

                //Or the easy way out with commons utils!
                //IOUtils.copy(this.iStream, System.out);


               catch (Exception e)
                    e.printStackTrace();  
              
        

        public InputStream getIStream() 
            return iStream;
        

        public void setIStream(InputStream stream) 
            iStream = stream;
        

        public String getCPrompt() 
            return cPrompt;
        

        public void setCPrompt(String prompt) 
            cPrompt = prompt;
        



【讨论】:

【参考方案15】:

Apache Commons Exec 可以帮助您做到这一点。

见http://commons.apache.org/proper/commons-exec/tutorial.html

String line = "your command line";
CommandLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(cmdLine);
System.out.println(exitValue);
System.out.println(outputStream.toString());

【讨论】:

【参考方案16】:

如果使用 Java 8,我会选择 Aleksander Blomskøld 的答案,即 p.waitFor(1, TimeUnit.MINUTE)

如果 Java 6/7 和使用 Swing,那么你可以使用 SwingWorker:

   final Process process = ...
   SwingWorker<Integer, Integer> sw = new SwingWorker<>() 
       @Override
       protected Integer doInBackground() throws Exception 
          process.waitFor();
          return process.exitValue();
       
   ;
   sw.execute();                
   int exitValue = sw.get(1, TimeUnit.SECONDS);
   if (exitValue == 0) 
       //everything was fine
    else 
       //process exited with issues
   

【讨论】:

【参考方案17】:

我知道这是一个很老的帖子;我在一个类似的项目中需要一些帮助,所以我想我可以提供一些我工作过的代码和那些工作的代码。

long current = System.currentTimeMillis();

ProcessBuilder pb  = new ProcessBuilder(arguments);
try
    pb.redirectErrorStream(true);
    process = pb.start();
    int c ;
    while((c = process.getInputStream().read()) != -1 )
        if(System.currentTimeMillis() - current < timeOutMilli) 
            result += (char)c;
        else throw new Exception();
    return result.trim();
    catch(Exception e)
        e.printStackTrace();
    
    return result;

希望这对未来有所帮助:D

【讨论】:

read() 方法不是阻塞调用,即等待数据可用的调用吗? - 如果是这种情况,那么这将永远等待直到写入数据,并且只有在接收到数据后才会评估超时。

以上是关于使用 Java 的 Runtime.exec() 时如何添加超时值?的主要内容,如果未能解决你的问题,请参考以下文章

Java Runtime.exec()的使用

使用 Java 的 Runtime.exec() 时如何添加超时值?

java Runtime.exec 异常

使用 Java Runtime.exec() 在 Hadoop 上运行命令

java.lang.Runtime.exec() Payload

如何通过在 java 中使用 Runtime.exec() 运行 sqlplus 来捕获 sqlplus 窗口的输出