前言
最近在開發一個系統時,需求是Java調用Python腳本,這裏我使用 Process process = Runtime.getRuntime().exec() 來調用,腳本用命令行能完整運行,但用Java調卻一直轉圈圈,等很久也不見結束.文章爲記錄…
參考文章
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}對象表示的進程具有終止 如果子進程,此方法立即返回已經終止。 如果子進程還沒有終止後,調用線程將被阻塞,直到子進程退出。