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}對象表示的進程具有終止 如果子進程,此方法立即返回已經終止。 如果子進程還沒有終止後,調用線程將被阻塞,直到子進程退出。

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