java 使用Runtime.getRuntime().exec()时Process.waitFor()死锁问题理解和解决

在这里插入图片描述

前言

最近在开发一个系统时,需求是Java调用Python脚本,这里我使用 Process process = Runtime.getRuntime().exec() 来调用,脚本用命令行能完整运行,但用Java调却一直转圈圈,等很久也不见结束.文章为记录…

参考文章

process参考
waitfor挂起解析

1.使用process调用py脚本

    public static ResultVO pyInvoke(String[] arguments) throws Exception {
        Process process = Runtime.getRuntime().exec(arguments);
        /**
         * GBK是防止Python输出乱码
         */
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
            String line = null;

            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
            //java代码中的process.waitFor()返回值为0表示我们调用python脚本成功,
            //返回值为1表示调用python脚本失败,这和我们通常意义上见到的0与1定义正好相反
            int re = process.waitFor();
            in.close();
            if (re == 1) {
                log.info("调用脚本失败");
                return ResultVoUtil.error("调用失败");
            } else {
                log.info("调用脚本成功");
                return ResultVoUtil.success("调用成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResultVoUtil.success();
    }

首先我们先使用 Process 创建出该对象,该对象我这里暂时使用了第一个参数为python3(通过数组传),第二个参数为脚本地址。后边主要就是打印 输入流,获取py脚本。其实到这里java 调用py脚本 就已经完 了,但是后续开发中遇到一种问题,就是程序莫名死锁,没有响应,于是使用debug 跟进代码,发现程序走到 waitfor 代码行的时候程序就出现了挂起的情况,于是google了一番,明白了其中的原因。

2. waitfor 问题描述分析

1.主进程中调用Runtime.getRuntime().exec() 会创建一个子进程,用于执行python脚本。子进程创建后会和主进程分别独立运行。

2.因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。

3.子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。

4.这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
5.这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

3.死锁问题的解决

基于上述分析,只要主进程在waitfor之前,能不断处理缓冲区中的数据就可以。因为,我们可以再waitfor之前,单独启两个额外的线程,分别用于处理InputStream和ErrorStream就可以解决.

try {

            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
            String line = null;

            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
            //获取进程的标准输入流
            final InputStream is1 = process.getInputStream();
            //获取进城的错误流
            final InputStream is2 = process.getErrorStream();
            //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
            new Thread() {
                public void run() {
                    BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
                    try {
                        String line1 = null;
                        while ((line1 = br1.readLine()) != null) {
                            if (line1 != null){}
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally{
                        try {
                            is1.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();

            new Thread() {
                public void  run() {
                    BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));
                    try {
                        String line2 = null ;
                        while ((line2 = br2.readLine()) !=  null ) {
                            if (line2 != null){}
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally{
                        try {
                            is2.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();

            //可能导致进程阻塞,甚至死锁
            int ret = process.waitFor();
            in.close();
            if (re == 1) {
                log.info("调用脚本失败");
                return ResultVoUtil.error("调用失败");
            } else {
                log.info("调用脚本成功");
                return ResultVoUtil.success("调用成功");
            }
        }catch (Exception ex){
            ex.printStackTrace();
            try{
                process.getErrorStream().close();
                process.getInputStream().close();
                process.getOutputStream().close();
            }
            catch(Exception ee){}
        }

如此便可以将 waitfor死锁问题避开,看完这个问题,总结一下,多看官方api注释…其实官方已经提示我们,如下 为 api注释

Causes the current thread to wait, if necessary, until the
* process represented by this {@code Process} object has
* terminated. This method returns immediately if the subprocess
* has already terminated. If the subprocess has not yet
* terminated, the calling thread will be blocked until the
* subprocess exits.
@return the exit value of the subprocess represented by this
* {@code Process} object. By convention, the value
* {@code 0} indicates normal termination.
* @throws InterruptedException if the current thread is
* {@linkplain Thread#interrupt() interrupted} by another
* thread while it is waiting, then the wait is ended and
* an {@link InterruptedException} is thrown.
如果需要,导致当前线程等待,直到此{@code Process}对象表示的进程具有终止 如果子进程,此方法立即返回已经终止。 如果子进程还没有终止后,调用线程将被阻塞,直到子进程退出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章