java調(diào)用process線程阻塞問題的解決
java調(diào)用process線程阻塞問題
項(xiàng)目需求中涉及java調(diào)用.bat文件進(jìn)行圖像處理,先直接上簡略版程序
public void draw(){
//調(diào)用bat腳本進(jìn)行圖像處理
Process process = null;
InputStream in = null;
try {
process = Runtime.getRuntime().exec("startup.bat");
//輸出測試
// in = process.getInputStream();
// String line;
// BufferedReader br = new BufferedReader(new InputStreamReader(in));
// while ((line = br.readLine()) != null) {
// System.out.println(line);
// }
//等待
process.waitFor();
} catch (Exception e) {
} finally {
process.destroy();
}
}
JAVA使用遇到的問題描述
一般需要調(diào)用系統(tǒng)命令時(shí),大部分人第一反應(yīng)肯定是使用Runtime.getRuntime().exec(command)返回一個(gè)process對象,再調(diào)用process.waitFor()來等待命令執(zhí)行結(jié)束,獲取執(zhí)行結(jié)果。
調(diào)試的時(shí)候發(fā)現(xiàn)異?,F(xiàn)象,process.waitFor();一直沒有結(jié)束,導(dǎo)致線程阻塞再次,強(qiáng)行關(guān)閉程序后,發(fā)現(xiàn)圖像處理只進(jìn)行了一部分。
根據(jù)現(xiàn)象并查看了JDK的幫助文檔,如下

如有必要,一直要等到由該 Process 對象表示的進(jìn)程已經(jīng)終止。如果已終止該子進(jìn)程,此方法立即返回。但是直接調(diào)用這個(gè)方法會(huì)導(dǎo)致當(dāng)前線程阻塞,直到退出子進(jìn)程。
對此JDK文檔上還有如此解釋:因?yàn)楸镜氐南到y(tǒng)對標(biāo)準(zhǔn)輸入和輸出所提供的緩沖池有效,所以錯(cuò)誤的對標(biāo)準(zhǔn)輸出快速的寫入何從標(biāo)準(zhǔn)輸入快速的讀入都有可能造成子進(jìn)程的阻塞,甚至死鎖。
Process執(zhí)行邏輯
* 主進(jìn)程中調(diào)用Runtime.exec會(huì)創(chuàng)建一個(gè)子進(jìn)程,用于執(zhí)行腳本。子進(jìn)程創(chuàng)建后會(huì)和主進(jìn)程分別獨(dú)立運(yùn)行。
* 創(chuàng)建的子進(jìn)程沒有自己的終端或控制臺(tái)。它的所有標(biāo)準(zhǔn) io(即 stdin、stdout 和 stderr)操作都將通過三個(gè)流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父進(jìn)程。父進(jìn)程使用這些流來提供到子進(jìn)程的輸入和獲得從子進(jìn)程的輸出。
* 這時(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)致死鎖。
解決方法:
在waitFor()之前,利用單獨(dú)兩個(gè)線程,分別處理process的getInputStream()和getErrorSteam(),防止緩沖區(qū)被撐滿,導(dǎo)致阻塞;
修改后代碼
public class test {
public void draw(){
//調(diào)用bat腳本進(jìn)行圖像處理
Process process = null;
InputStream in = null;
try {
process = Runtime.getRuntime().exec("startup.bat");
//輸出測試
// in = process.getInputStream();
// String line;
// BufferedReader br = new BufferedReader(new InputStreamReader(in));
// while ((line = br.readLine()) != null) {
// System.out.println(line);
// }
//新啟兩個(gè)線程
new DealProcessSream(process.getInputStream()).start();
new DealProcessSream(process.getErrorStream()).start();
process.waitFor();
} catch (Exception e) {
} finally {
process.destroy();
}
}
}
public class DealProcessSream extends Thread {
private InputStream inputStream;
public DealProcessSream(InputStream inputStream) {
this.inputStream = inputStream;
}
public void run() {
InputStreamReader inputStreamReader = null;
BufferedReader br = null;
try {
inputStreamReader = new InputStreamReader(
inputStream);
br = new BufferedReader(inputStreamReader);
// 打印信息
// String line = null;
// while ((line = br.readLine()) != null) {
// System.out.println(line);
// }
// 不打印信息
while (br.readLine() != null);
} catch (IOException ioe) {
ioe.printStackTrace();
}finally {
try {
br.close();
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Process對象.waitFor()的阻塞問題(坑)
有時(shí)需要在程序中調(diào)用可執(zhí)行程序或腳本命令:
Process process = Runtime.getRuntime().exec(shPath); int exitCode = process .waitFor();
Runtime.getRuntime()返回當(dāng)前應(yīng)用程序的Runtime對象,該對象的exec()方法指示Java虛擬機(jī)創(chuàng)建一個(gè)子進(jìn)程執(zhí)行指定的可執(zhí)行程序,
并返回與該子進(jìn)程對應(yīng)的Process對象實(shí)例。通過Process可以控制該子進(jìn)程的執(zhí)行或獲取該子進(jìn)程的信息。
它的所有標(biāo)準(zhǔn)io(即stdin,stdout,stderr)操作都將通過三個(gè)流(getOutputStream(),getInputStream(),getErrorStream())重定向到父進(jìn)程。
父進(jìn)程使用這些流來提供到子進(jìn)程的輸入和獲得從子進(jìn)程的輸出。因?yàn)橛行┍緳C(jī)平臺(tái)僅針對標(biāo)準(zhǔn)輸入和輸出流提供有限的緩沖區(qū)大小,如果讀
寫子進(jìn)程的輸出流或輸入流出現(xiàn)失敗,則可能導(dǎo)致子進(jìn)程阻塞,甚至產(chǎn)生死鎖。(如果程序不斷在向標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流寫數(shù)據(jù),而JVM不讀取的話,當(dāng)緩沖區(qū)滿之后將無法繼續(xù)寫入數(shù)據(jù),最終造成阻塞在waifor()這里。)
process .getErrorStream():獲得子進(jìn)程的錯(cuò)誤輸出流
process .getInputStream():獲得子進(jìn)程的普通輸出流
簡單示例:
Process shellProcess = null;
try {
shellProcess = Runtime.getRuntime().exec(shPath);
shellErrorResultReader = new BufferedReader(new InputStreamReader(shellProcess.getErrorStream()));
shellInfoResultReader = new BufferedReader(new InputStreamReader(shellProcess.getInputStream()));
String infoLine;
while ((infoLine = shellInfoResultReader.readLine()) != null) {
logger.info("腳本文件執(zhí)行信息:{}", infoLine);
}
String errorLine;
while ((errorLine = shellErrorResultReader.readLine()) != null) {
logger.warn("腳本文件執(zhí)行信息:{}", errorLine);
}
// 等待程序執(zhí)行結(jié)束并輸出狀態(tài)
exitCode = shellProcess.waitFor();
if (0 == exitCode) {
logger.info("腳本文件執(zhí)行成功:" + exitCode);
} else {
logger.error("腳本文件執(zhí)行失敗:" + exitCode);
}
} catch (Exception e) {
logger.error("shell腳本執(zhí)行錯(cuò)誤", e);
} finally {
if (null != shellInfoResultReader) {
try {
shellInfoResultReader.close();
} catch (IOException e) {
logger.error("流文件關(guān)閉異常:", e);
}
}
if (null != shellErrorResultReader) {
try {
shellErrorResultReader.close();
} catch (IOException e) {
logger.error("流文件關(guān)閉異常:", e);
}
}
if (null != shellProcess) {
shellProcess.destroy();
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis分布式鎖實(shí)現(xiàn)方式及超時(shí)問題解決
這篇文章主要介紹了Redis分布式鎖實(shí)現(xiàn)方式及超時(shí)問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Mybatis使用foreach批量更新數(shù)據(jù)報(bào)無效字符錯(cuò)誤問題
這篇文章主要介紹了Mybatis使用foreach批量更新數(shù)據(jù)報(bào)無效字符錯(cuò)誤問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Java多線程并發(fā)編程 Synchronized關(guān)鍵字
現(xiàn)有一成員變量 Test,當(dāng)線程 A 調(diào)用 Test 的 synchronized 方法,線程 A 獲得 Test 的同步鎖,同時(shí),線程 B 也去調(diào)用 Test 的 synchronized 方法,此時(shí)線程 B 無法獲得 Test 的同步鎖,必須等待線程 A 釋放 Test 的同步鎖才能獲得從而執(zhí)行對應(yīng)方法的代碼2017-05-05
java+SpringBoot設(shè)計(jì)實(shí)現(xiàn)評(píng)教系統(tǒng)
這篇文章主要介紹了用過Java SpringBoot實(shí)現(xiàn)一個(gè)簡單的評(píng)價(jià)系統(tǒng),可以做到學(xué)生管理、教師管理、評(píng)教管理、指標(biāo)管理、課程管理等。感興趣的可以了解一下2021-12-12
使用Filter攔截器如何實(shí)現(xiàn)請求跨域轉(zhuǎn)發(fā)
這篇文章主要介紹了使用Filter攔截器如何實(shí)現(xiàn)請求跨域轉(zhuǎn)發(fā),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

