欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用Runtime 调用Process.waitfor导致的阻塞问题

程序员文章站 2022-03-04 14:55:57
目录1. 关于runtime类的小知识2. runtime的几个重要的重载方法3. runtime的使用方式4. 卡死原因5. 解决方案6. runtime最优雅的调用方式1. 关于runtime类的...

1. 关于runtime类的小知识

  • runtime.getruntime()可以取得当前jvm的运行时环境,这也是在java中唯一一个得到运行时环境的方法
  • runtime中的exit方法是退出jvm

2. runtime的几个重要的重载方法

方法名 作用
exec(string command); 在单独的进程中执行指定的字符串命令。
exec(string command, string[] envp) 在指定环境的单独进程中执行指定的字符串命令。
exec(string[] cmdarray, string[] envp, file dir) 在指定环境和工作目录的独立进程中执行指定的命令和变量
exec(string command, string[] envp, file dir) 在有指定环境和工作目录的独立进程中执行指定的字符串命令。

runtime类的重要的方法还有很多,简单列举几个

  • exit(int status):终止当前正在运行的 java 虚拟机
  • freememory():返回 java 虚拟机中的空闲内存量。
  • load(string filename): 加载作为动态库的指定文件名。
  • loadlibrary(string libname): 加载具有指定库名的动态库。

3. runtime的使用方式

错误的使用exitvalue()

  public static void main(string[] args) throws ioexception {
        string command = "ping www.baidu.com";
        process process = runtime.getruntime().exec(command);
        int i = process.exitvalue();
        system.out.println("字进程退出值:"+i);
    }

输出:

exception in thread "main" java.lang.illegalthreadstateexception: process has not exited
at java.lang.processimpl.exitvalue(processimpl.java:443)
at com.lirong.think.runtime.processutils.main(processutils.java:26)

原因:

exitvalue()方法是非阻塞的,在调用这个方法时cmd命令并没有返回所以引起异常。阻塞形式的方法是waitfor,它会一直等待外部命令执行完毕,然后返回执行的结果。

修改后的版本:

  public static void main(string[] args) throws ioexception {
        string command = "javac";
        process process = runtime.getruntime().exec(command);
        process.waitfor();
        process.destroy();
        int i = process.exitvalue();
        system.out.println("字进程退出值:"+i);
    }

此版本已然可以正常运行,但当主线程和子线程有很多交互的时候还是会出问题,会出现卡死的情况。

4. 卡死原因

  • 主进程中调用runtime.exec会创建一个子进程,用于执行cmd命令。子进程创建后会和主进程分别独立运行。
  • 因为主进程需要等待脚本执行完成,然后对命令返回值或输出进行处理,所以这里主进程调用process.waitfor等待子进程完成。
  • 运行此cmd命令可以知道:子进程执行过程就是打印信息。主进程中可以通过process.getinputstream和process.geterrorstream获取并处理。
  • 这时候子进程不断向主进程发生数据,而主进程调用process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
  • 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

5. 解决方案

不断的读取消耗缓冲区的数据,以至子进程不会挂起,下面是具体代码:

/**
 * @author lirong
 * @desc cmd命令测试
 * @date 2019/06/13 20:50
 */
@slf4j
public class processutils {
    public static void main(string[] args) throws ioexception, interruptedexception {
        string command = "ping www.baidu.com";
        process process = runtime.getruntime().exec(command);
        readstreaminfo(process.getinputstream(), process.geterrorstream());
        int exit = process.waitfor();
        process.destroy();
        if (exit == 0) {
            log.debug("子进程正常完成");
        } else {
            log.debug("子进程异常结束");
        }
    }
    /**
     * 读取runtime.exec运行子进程的输入流 和 异常流
     * @param inputstreams 输入流
     */
    public static void readstreaminfo(inputstream... inputstreams){
        executorservice executorservice = executors.newfixedthreadpool(inputstreams.length);
        for (inputstream in : inputstreams) {
            executorservice.execute(new mythread (in));
        }
        executorservice.shutdown();
    }
}
/**
 * @author lirong
 * @desc
 * @date 2019/06/13 21:25
 */
@slf4j
public class mythread implements runnable {
    private inputstream in;
    public mythread(inputstream in){
        this.in = in;
    }
    @override
    public void run() {
        try{
            bufferedreader br = new bufferedreader(new inputstreamreader(in, "gbk"));
            string line = null;
            while((line = br.readline())!=null){
                log.debug(" inputstream: " + line);
            }
        }catch (ioexception e){
            e.printstacktrace();
        }finally {
            try {
                in.close();
            } catch (ioexception e) {
                e.printstacktrace();
            }
        }
    }
}

写到这里大家以为都结束了哇,并没有,哈哈哈,真实的生成环境总能给你带来很多神奇的问题,runtime不仅可以直接调用cmd执行命令,还可以调用其他.exe程序执行命令。

所以纵使你读取了缓冲区的数据,你的程序依然可能会被卡死,因为有可能你的缓冲区根本就没有数据,而是你的.exe程序卡主了。嗯,所以为你以防万一,你还需要设置超时。

6. runtime最优雅的调用方式

/**
 * @author lirong
 * @desc
 * @date 2019/06/13 20:50
 */
@slf4j
public class processutils {
   /**
     * @param timeout 超时时长
     * @param filedir 所运行程序路径
     * @param command 程序所要执行的命令
     * 运行一个外部命令,返回状态.若超过指定的超时时间,抛出timeoutexception
     */
    public static int executeprocess(final long timeout, file filedir, final string[] command)
            throws ioexception, interruptedexception, timeoutexception {
        process process = runtime.getruntime().exec(command, null, filedir);
        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.destroy();
        }
    }
    
    private static class worker extends thread {
        private final process process;
        private integer exit;
        private worker(process process) {
            this.process = process;
        }
        @override
        public void run() {
            inputstream errorstream = null;
            inputstream inputstream = null;
            try {
                errorstream = process.geterrorstream();
                inputstream = process.getinputstream();
                readstreaminfo(errorstream, inputstream);
                exit = process.waitfor();
                process.destroy();
                if (exit == 0) {
                    log.debug("子进程正常完成");
                } else {
                    log.debug("子进程异常结束");
                }
            } catch (interruptedexception ignore) {
                return;
            }
        }
    }
    /**
     * 读取runtime.exec运行子进程的输入流 和 异常流
     * @param inputstreams 输入流
     */
    public static void readstreaminfo(inputstream... inputstreams){
        executorservice executorservice = executors.newfixedthreadpool(inputstreams.length);
        for (inputstream in : inputstreams) {
            executorservice.execute(new mythread(in));
        }
        executorservice.shutdown();
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。