使用Runtime 調(diào)用Process.waitfor導致的阻塞問題
1. 關于Runtime類的小知識
Runtime.getRuntime()
可以取得當前JVM的運行時環(huán)境,這也是在Java中唯一一個得到運行時環(huán)境的方法Runtime
中的exit方法是退出JVM
2. Runtime的幾個重要的重載方法
方法名 | 作用 |
---|---|
exec(String command); | 在單獨的進程中執(zhí)行指定的字符串命令。 |
exec(String command, String[] envp) | 在指定環(huán)境的單獨進程中執(zhí)行指定的字符串命令。 |
exec(String[] cmdarray, String[] envp, File dir) | 在指定環(huán)境和工作目錄的獨立進程中執(zhí)行指定的命令和變量 |
exec(String command, String[] envp, File dir) | 在有指定環(huán)境和工作目錄的獨立進程中執(zhí)行指定的字符串命令。 |
Runtime類的重要的方法還有很多,簡單列舉幾個
exit(int status)
:終止當前正在運行的 Java 虛擬機freeMemory()
:返回 Java 虛擬機中的空閑內(nèi)存量。load(String filename)
: 加載作為動態(tài)庫的指定文件名。loadLibrary(String libname)
: 加載具有指定庫名的動態(tài)庫。
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()方法是非阻塞的,在調(diào)用這個方法時cmd命令并沒有返回所以引起異常。阻塞形式的方法是waitFor,它會一直等待外部命令執(zhí)行完畢,然后返回執(zhí)行的結果。
修改后的版本:
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); }
此版本已然可以正常運行,但當主線程和子線程有很多交互的時候還是會出問題,會出現(xiàn)卡死的情況。
4. 卡死原因
- 主進程中調(diào)用Runtime.exec會創(chuàng)建一個子進程,用于執(zhí)行cmd命令。子進程創(chuàng)建后會和主進程分別獨立運行。
- 因為主進程需要等待腳本執(zhí)行完成,然后對命令返回值或輸出進行處理,所以這里主進程調(diào)用Process.waitfor等待子進程完成。
- 運行此cmd命令可以知道:子進程執(zhí)行過程就是打印信息。主進程中可以通過Process.getInputStream和Process.getErrorStream獲取并處理。
- 這時候子進程不斷向主進程發(fā)生數(shù)據(jù),而主進程調(diào)用Process.waitfor后已掛起。當前子進程和主進程之間的緩沖區(qū)塞滿后,子進程不能繼續(xù)寫數(shù)據(jù),然后也會掛起。
- 這樣子進程等待主進程讀取數(shù)據(jù),主進程等待子進程結束,兩個進程相互等待,最終導致死鎖。
5. 解決方案
不斷的讀取消耗緩沖區(qū)的數(shù)據(jù),以至子進程不會掛起,下面是具體代碼:
/** * @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(); } } } }
寫到這里大家以為都結束了哇,并沒有,哈哈哈,真實的生成環(huán)境總能給你帶來很多神奇的問題,Runtime不僅可以直接調(diào)用CMD執(zhí)行命令,還可以調(diào)用其他.exe程序執(zhí)行命令。
所以縱使你讀取了緩沖區(qū)的數(shù)據(jù),你的程序依然可能會被卡死,因為有可能你的緩沖區(qū)根本就沒有數(shù)據(jù),而是你的.exe程序卡主了。嗯,所以為你以防萬一,你還需要設置超時。
6. Runtime最優(yōu)雅的調(diào)用方式
/** * @author lirong * @desc * @date 2019/06/13 20:50 */ @Slf4j public class ProcessUtils { /** * @param timeout 超時時長 * @param fileDir 所運行程序路徑 * @param command 程序所要執(zhí)行的命令 * 運行一個外部命令,返回狀態(tài).若超過指定的超時時間,拋出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(); } }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Spring Boot使用Thymeleaf + Gradle構建war到Tomcat
今天小編就為大家分享一篇關于Spring Boot使用Thymeleaf + Gradle構建war到Tomcat,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12SpringBoot集成百度AI實現(xiàn)人臉識別的項目實踐
本文主要介紹了SpringBoot集成百度AI實現(xiàn)人臉識別的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-05-05redis實現(xiàn)隊列的阻塞、延時、發(fā)布和訂閱
本文主要介紹了redis實現(xiàn)隊列的阻塞、延時、發(fā)布和訂閱,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-06-06