使用Runtime 調(diào)用Process.waitfor導(dǎo)致的阻塞問(wèn)題
1. 關(guān)于Runtime類的小知識(shí)
Runtime.getRuntime()可以取得當(dāng)前JVM的運(yùn)行時(shí)環(huán)境,這也是在Java中唯一一個(gè)得到運(yùn)行時(shí)環(huán)境的方法Runtime中的exit方法是退出JVM
2. Runtime的幾個(gè)重要的重載方法
| 方法名 | 作用 |
|---|---|
| exec(String command); | 在單獨(dú)的進(jìn)程中執(zhí)行指定的字符串命令。 |
| exec(String command, String[] envp) | 在指定環(huán)境的單獨(dú)進(jìn)程中執(zhí)行指定的字符串命令。 |
| exec(String[] cmdarray, String[] envp, File dir) | 在指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的命令和變量 |
| exec(String command, String[] envp, File dir) | 在有指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的字符串命令。 |
Runtime類的重要的方法還有很多,簡(jiǎn)單列舉幾個(gè)
exit(int status):終止當(dāng)前正在運(yùn)行的 Java 虛擬機(jī)freeMemory():返回 Java 虛擬機(jī)中的空閑內(nèi)存量。load(String filename): 加載作為動(dòng)態(tài)庫(kù)的指定文件名。loadLibrary(String libname): 加載具有指定庫(kù)名的動(dòng)態(tài)庫(kù)。
3. Runtime的使用方式
錯(cuò)誤的使用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("字進(jìn)程退出值:"+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)用這個(gè)方法時(shí)cmd命令并沒(méi)有返回所以引起異常。阻塞形式的方法是waitFor,它會(huì)一直等待外部命令執(zhí)行完畢,然后返回執(zhí)行的結(jié)果。
修改后的版本:
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("字進(jìn)程退出值:"+i);
}
此版本已然可以正常運(yùn)行,但當(dāng)主線程和子線程有很多交互的時(shí)候還是會(huì)出問(wèn)題,會(huì)出現(xiàn)卡死的情況。
4. 卡死原因
- 主進(jìn)程中調(diào)用Runtime.exec會(huì)創(chuàng)建一個(gè)子進(jìn)程,用于執(zhí)行cmd命令。子進(jìn)程創(chuàng)建后會(huì)和主進(jìn)程分別獨(dú)立運(yùn)行。
- 因?yàn)橹鬟M(jìn)程需要等待腳本執(zhí)行完成,然后對(duì)命令返回值或輸出進(jìn)行處理,所以這里主進(jìn)程調(diào)用Process.waitfor等待子進(jìn)程完成。
- 運(yùn)行此cmd命令可以知道:子進(jìn)程執(zhí)行過(guò)程就是打印信息。主進(jìn)程中可以通過(guò)Process.getInputStream和Process.getErrorStream獲取并處理。
- 這時(shí)候子進(jìn)程不斷向主進(jìn)程發(fā)生數(shù)據(jù),而主進(jìn)程調(diào)用Process.waitfor后已掛起。當(dāng)前子進(jìn)程和主進(jìn)程之間的緩沖區(qū)塞滿后,子進(jìn)程不能繼續(xù)寫數(shù)據(jù),然后也會(huì)掛起。
- 這樣子進(jìn)程等待主進(jìn)程讀取數(shù)據(jù),主進(jìn)程等待子進(jìn)程結(jié)束,兩個(gè)進(jìn)程相互等待,最終導(dǎo)致死鎖。
5. 解決方案
不斷的讀取消耗緩沖區(qū)的數(shù)據(jù),以至子進(jìn)程不會(huì)掛起,下面是具體代碼:
/**
* @author lirong
* @desc CMD命令測(cè)試
* @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("子進(jìn)程正常完成");
} else {
log.debug("子進(jìn)程異常結(jié)束");
}
}
/**
* 讀取RunTime.exec運(yùn)行子進(jìn)程的輸入流 和 異常流
* @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();
}
}
}
}
寫到這里大家以為都結(jié)束了哇,并沒(méi)有,哈哈哈,真實(shí)的生成環(huán)境總能給你帶來(lái)很多神奇的問(wèn)題,Runtime不僅可以直接調(diào)用CMD執(zhí)行命令,還可以調(diào)用其他.exe程序執(zhí)行命令。
所以縱使你讀取了緩沖區(qū)的數(shù)據(jù),你的程序依然可能會(huì)被卡死,因?yàn)橛锌赡苣愕木彌_區(qū)根本就沒(méi)有數(shù)據(jù),而是你的.exe程序卡主了。嗯,所以為你以防萬(wàn)一,你還需要設(shè)置超時(shí)。
6. Runtime最優(yōu)雅的調(diào)用方式
/**
* @author lirong
* @desc
* @date 2019/06/13 20:50
*/
@Slf4j
public class ProcessUtils {
/**
* @param timeout 超時(shí)時(shí)長(zhǎng)
* @param fileDir 所運(yùn)行程序路徑
* @param command 程序所要執(zhí)行的命令
* 運(yùn)行一個(gè)外部命令,返回狀態(tài).若超過(guò)指定的超時(shí)時(shí)間,拋出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("子進(jìn)程正常完成");
} else {
log.debug("子進(jìn)程異常結(jié)束");
}
} catch (InterruptedException ignore) {
return;
}
}
}
/**
* 讀取RunTime.exec運(yùn)行子進(jìn)程的輸入流 和 異常流
* @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();
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot Thymeleaf數(shù)字對(duì)象使用方法
這篇文章主要介紹了Springboot Thymeleaf數(shù)字對(duì)象使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2007-09-09
Java對(duì)象池pool2分析PooledObjectFactory過(guò)程
文章介紹了Java中對(duì)象池化技術(shù)的背景,以Apache的Pool2庫(kù)為例,詳細(xì)講解了GenericObjectPool的構(gòu)造函數(shù)參數(shù)和PooledObjectFactory接口的實(shí)現(xiàn),通過(guò)商場(chǎng)里的共享充電寶的比喻,說(shuō)明了池化思維的應(yīng)用2025-02-02
MyBatis-Plus實(shí)現(xiàn)多數(shù)據(jù)源的示例代碼
這篇文章主要介紹了MyBatis-Plus實(shí)現(xiàn)多數(shù)據(jù)源的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
SpringMVC中的DispatcherServlet初始化流程詳解
這篇文章主要介紹了SpringMVC中的DispatcherServlet初始化流程詳解,DispatcherServlet這個(gè)前端控制器是一個(gè)Servlet,所以生命周期和普通的Servlet是差不多的,在一個(gè)Servlet初始化的時(shí)候都會(huì)調(diào)用該Servlet的init()方法,需要的朋友可以參考下2023-12-12

