場景:
java在企業級項目開發中,需要調用exe、shell這樣的程序或腳本。
在Java中提供了兩種方法來啓動其他程序:
(1) 使用Runtime的exec()方法
(2) 使用ProcessBuilder的start()方法 。
Runtime和ProcessBulider提供了不同的方式來啓動程序,設置啓動參數、環境變量和工作目錄。但是這兩種方法都會返回一個用於管理操作系統進程的Process對象。
此次分析第一種方法。
注意:
在Java中,調用runtime線程執行腳本是非常消耗資源的,所以建議不要頻繁使用!
關於waitFor() 阻塞/鎖死 問題:
JDK幫助文檔上這麼說:如有必要,一直要等到由該 Process 對象表示的進程已經終止。如果已終止該子進程,此方法立即返回。但是直接調用這個方法會導致當前線程阻塞,直到退出子進程。對此JDK文檔上還有如此解釋:因爲本地的系統對標準輸入和輸出所提供的緩衝池有效,所以錯誤的對標準輸出快速的寫入何從標準輸入快速的讀入都有可能造成子進程的所,甚至死鎖。好了,問題的關鍵在緩衝區這個地方:可執行程序的標準輸出比較多,而運行窗口的標準緩衝區不夠大,所以發生阻塞。接着來分析緩衝區,哪來的這個東西,當Runtime對象調用exec(cmd)後,JVM會啓動一個子進程,該進程會與JVM進程建立三個管道連接:標準輸入、標準輸出和標準錯誤流。假設該程序不斷在向標準輸出流和標準錯誤流寫數據,而JVM不讀取的話,當緩衝區滿之後將無法繼續寫入數據,最終造成阻塞在waitfor()這裏。 知道問題所在,我們解決問題就好辦了。查看網上說的方法多數是開兩個線程在waitfor()命令之前讀出窗口的標準輸出緩衝區和標準錯誤流的內容
java簡單工具類代碼:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import java.io.*;
@Slf4j
public class CommandUtil {
/**
* 執行 inux/cmd 命令
*
* @param command 命令
* @return
*/
public static boolean executeCommand(String command) {
return executeCommand(command, null);
}
/**
* 指定目錄執行 linux/cmd 命令
*
* @param command 命令
* @param file 目錄
* @return
*/
public static boolean executeCommand(String command, File file) {
BufferedReader reader = null;
InputStreamReader inputStreamReader = null;
Process p;
try {
//判斷是操作系統是linux還是windows
String[] comds;
if (System.getProperties().get("os.name").toString().toUpperCase().indexOf("WINDOWS") >= 0) {
log.info("當前操作系統:windows");
comds = new String[]{"cmd", "/c", command};
} else {
log.info("當前操作系統:linux");
comds = new String[]{"/bin/sh", "-c", command};
}
// 開始執行命令
log.info("執行命令:" + command);
if (file!=null) {
p = Runtime.getRuntime().exec(comds, null, file);
} else {
p = Runtime.getRuntime().exec(comds);
}
//開啓線程監聽(此處解決 waitFor() 阻塞/鎖死 問題)
new RunThread(p.getInputStream(), "INFO").start();
new RunThread(p.getErrorStream(), "ERROR").start();
int flag = p.waitFor();
return true;
} catch (IOException e) {
log.error("Proto2Html 執行命令IO異常!", e);
return false;
} catch (InterruptedException e) {
log.error("Proto2Html 執行命令中斷異常!", e);
return false;
} catch (Exception e) {
log.error("Proto2Html 執行命令出現異常!", e);
return false;
} finally {
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(inputStreamReader);
}
}
}
/**
* 監聽線程
*/
@Slf4j
class RunThread extends Thread {
InputStream is;
String printType;
RunThread(InputStream is, String printType) {
this.is = is;
this.printType = printType;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is, "GBK");
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine())!=null){
log.info(printType + ">" + line);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
測試案例:
public static void main(String[] args) {
//進入demo目錄執行 maven 打包命令
File file = new File("d:/workRepository/demo");
String command = "mvn clean package";
CommandUtil.executeCommand(command,file);
}
RunTime.getRuntime().exec() 用法:
// 在單獨的進程中執行指定的字符串命令
public Process exec(String command);
// 在單獨的進程中執行指定命令和變量
public Process exec(String[] cmdArray);
// 在指定環境的獨立進程中執行指定命令和變量
public Process exec(String command,String[] envp);
// 在指定環境的獨立進程中執行指定命令和變量
public Process exec(String[] cmdArray,String[] envp);
// 在有指定的環境和工作目錄的獨立進程中執行指定的字符串命令
public Process exec(String command,String[] encp,File dir);
// 在指定環境和工作目錄的獨立進程中執行指定的命令和變量
public Process exec(String[] cmdarray,String[] envp,File dir);
深入:
Process的幾種方法
public static void main(String[] args) {
Process p = Runtime.getRuntime().exec(comds);
//1、殺掉子進程
p.destroy();
//2、返回子進程的出口值,值0表示正常終止
p.exitValue();
//3、獲取子進程的錯誤流
p.getErrorStream();
//4、獲取子進程的輸入流
p.getInputStream();
//5、獲取子進程的輸出流
p.getOutputStream();
/*6、導致當前線程等待,如有必要,一直要等到由該Process對象表示的進程已經終止。
如果已終止該子進程,此方法立即返回。如果沒有終止該子進程,
調用的線程將被阻塞,直到退出子進程,根據管理,0表示正常終止。*/
p.waitFor();
}