现在的位置: 首页 > 综合 > 正文

正确的调用系统命令——为Process.waitFor设置超时以及其他

2014年08月29日 ⁄ 综合 ⁄ 共 2990字 ⁄ 字号 评论关闭

Java中在阻塞调用系统命令的时候,一般是使用Runtime.getRuntime().exec(command)返回一个process对象,再调用Process.waitFor()来等待命令执行结束,获取执行结果。

然而这样简单的调用也是有坑的,有几个地方需要小心留意。

一个是Runtime.getRuntime().exec(command)这个调用对于可执行文件路径或者参数中有空格等特殊字符的情况不能处理,必须手工对其进行转义。推荐使用ProcessBuilder这个新加入的类来替换Runtime.getRuntime().exec,可以自动处理特殊字符,并且接口也要更加丰富和方便使用。

然后,即使只是调用了很简单的脚本命令,在调用Process.waitFor()后都有可能发生无休止或者接近于无休止的阻塞,所以在代码中加入超时控制是必须的。但是Process.waitFor()本身并不支持超时时间设置,一个方法是改用非阻塞的Process.exitValue()方法,然后轮询检查进程状态,这种方式比较消耗CPU,以至于轮询间隔也不能设置得太小,总归不是很完美。另外就是另起一个线程来调用程序,在主线程中发现超时的时候,直接调用process.destroy()终止进程。

还有就是在捕获程序输出的问题。如果要读取程序的删除结果,一定要在调用Process.waitFor()前将程序的stdout和stderr都读完,否则就有可能因为pipe的缓冲区不够,被调用的系统命令阻塞在标准输出和标准错误输出上。Windows因为这个缓冲区的默认值比较小更容易出现这个问题。
需要注意读取程序的stdout和stderr都是阻塞的操作,这意味着必须在两个线程里分别读取,而不是在一个线程里一次读取,否则还是有可能出现阻塞的情况:比如先读取stdout再读取stderr,如果程序的stderr输出已经填满了缓冲区,程序就会阻塞不继续执行,但是java线程又阻塞在读取stdout上,只有stdout结束了才会去读取stderr。结果就是互相等待着的过程中哦给你程序卡死了。
为了不让程序变得过于复杂,我们可以把程序的stderr重定向到stdout中,这样只要读取stdout一个就好了。缺点就是没有区分出错的输出信息和正常的输出信息。

最后使用的代码如下:

/**
 * 一个进程调用工具.
 *
 * @author dongliu
 *
 */
public class ProcessUtils {

    /**
     * 运行一个外部命令,返回状态.若超过指定的超时时间,抛出TimeoutException
     * 
     */
    public static ProcessStatus execute(final long timeout, final String... command)
            throws IOException, InterruptedException, TimeoutException {

        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true);
        Process process = pb.start();

        Worker worker = new Worker(process);
        worker.start();
        ProcessStatus ps = worker.getProcessStatus();
        try {
            worker.join(timeout);
            if (ps.exitCode == ProcessStatus.CODE_STARTED) {
                // not finished
                worker.interrupt();
                throw new TimeoutException();
            } else {
                return ps;
            }
        } catch (InterruptedException e) {
            // canceled by other thread.
            worker.interrupt();
            throw e;
        } finally {
            process.destroy();
        }
    }

    private static class Worker extends Thread {
        private final Process process;
        private ProcessStatus ps;

        private Worker(Process process) {
            this.process = process;
            this.ps = new ProcessStatus();
        }

        public void run() {
            try {
                InputStream is = process.getInputStream();
                try {
                    ps.output = Utils.toString(is);
                } catch (IOException ignore) { }
                ps.exitCode = process.waitFor();

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        public ProcessStatus getProcessStatus() {
            return this.ps;
        }
    }
    
    public static class ProcessStatus {
        public static final int CODE_STARTED = -257;
        public volatile int exitCode;
        public volatile String output;
    }

}



希望这个方法有用,在调一个把word转成pdf pdf转成swf的项目。。 
在晚上找的 ,还没有测试 一会回家测试看看。


转载:http://www.dongliu.net/post/496142

抱歉!评论已关闭.