一、問題再現
使用wkhtmltopdf插件做html轉換pdf,列印條碼標簽,
列印兩三張沒問題,列印多了程式就出現這種卡死的情況,等很久程式也沒有反應,也不報錯,后臺也沒有程式輸出,試了幾次都是這種情況,感覺程式一直在等待,于是懷疑是死鎖,

二、查找原因
經過查找資料了解到:Process.waitFor可能導致死鎖?
因為本地的系統對標準輸入和輸出所提供的緩沖池有限,所以錯誤的對標準輸出快速的寫入和從標準輸入快速的讀入都有可能造成子行程死鎖,問題的關鍵在緩沖區這個地方:可執行程式的標準輸出比較多,而運行視窗的標準緩沖區不夠大,所以發生阻塞,接著來分析緩沖區,當Runtime物件呼叫exec(cmd)后,JVM會啟動一個子行程,該行程會與JVM行程建立三個管道連接:標準輸入,標準輸出和標準錯誤流,假設該程式不斷在向標準輸出流和標準錯誤流寫資料,而JVM不讀取的話,當緩沖區滿之后將無法繼續寫入資料,最終造成阻塞在waitfor()這里,
需要注意讀取程式的stdout和stderr都是阻塞的操作,這意味著必須在兩個執行緒里分別讀取,而不是在一個執行緒里一次讀取,否則還是有可能出現阻塞的情況:比如先讀取stdout再讀取stderr,如果程式的stderr輸出已經填滿了緩沖區,程式就會阻塞不繼續執行,但是java執行緒又阻塞在讀取stdout上,只有stdout結束了才會去讀取stderr,結果就是互相等待著的程序中哦給你程式卡死了
1. 當我們使用Runtime.exec執行命令時,JAVA的執行緒會創建一個子行程,用于執行命令,而且子行程和JAVA執行緒會分別獨立運行,
2. JAVA執行緒需要等待命令的執行完成,對命令的日志和回傳值進行處理,所以我們在JAVA執行緒中呼叫Process.waitFor掛起來等待子行程完成,
3. 子行程執行時,不斷的列印日志資訊,我們通過Process.getInputStream和 Process.getErrorStream進行獲取正常輸出日志和錯誤日志進行處理,
4. 這個時候子行程不斷的向JAVA執行緒寫入資料,而JAVA執行緒呼叫Process.waitFor后已經阻塞掛起,而子行程在不斷的向JAVA執行緒進行寫入資料,當我們的Process.getInputStream的buffer緩沖區被寫滿,而JAVA執行緒依然掛起并未消費buffer中的資料,導致子行程無法繼續向buffer緩沖區中繼續寫入資料,導致子行程也掛起, 5. 這個時候JAVA執行緒和子行程都處于掛起的狀態,JAVA執行緒等待子行程的結束,子行程等待JAVA執行緒對buffer緩沖區中的資料進行消費,兩者在相互等待導致死鎖,
死鎖原理圖

三、解決方案
1.思路
既然是由于buffer緩沖區的資料沒有消費導致子行程掛起,那么我們從這里下手,
a. 消費buffer緩沖區中的資料
b. 當JAVA執行緒呼叫Process.waitFor后,執行緒會進行掛起,那我們就使用多執行緒進行消費資料,
2.正常流程圖

3.代碼實作
容易出現死鎖的舊代碼
public static boolean execCommond(String... args) {
boolean flg = true;
Runtime run = Runtime.getRuntime();
try {
Process p;
if (args != null && args.length == 1) {
p = run.exec(args[0]);
} else {
p = run.exec(args);
}
LoggerUtils.info(CmdExecUtils.class, p.getInputStream() + "....getInputStream..");
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
LoggerUtils.info(CmdExecUtils.class, inBr + "....inBr..");
String lineStr;
while ((lineStr = inBr.readLine()) != null) {
LoggerUtils.info(CmdExecUtils.class, lineStr );
System.out.println(lineStr);// 列印輸出資訊
}
if (p.waitFor() != 0) {
if (p.exitValue() == 1) {
logger.info("==================================命令執行失敗!");
flg = false;
}
}
inBr.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
flg = false;
}
return flg;
}
解決死鎖后的代碼:
private static ThreadPoolExecutor executor;
static {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("cmd-pool-%d").build();
//根據實際情況創建執行緒池
executor = new ThreadPoolExecutor(6, 10, 5,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024),
namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
}
/**
* 流處理
* @param stream
*/
private static void clearStream(InputStream stream) {
//處理buffer的執行緒
executor.execute(new Runnable() {
@Override
public void run() {
String line = null;
try (BufferedReader in = new BufferedReader(new InputStreamReader(stream));) {
while ((line = in.readLine()) != null) {
LoggerUtils.debug(CmdExecUtils.class,line);
}
} catch (IOException e) {
LoggerUtils.error(CmdExecUtils.class,"exec error : {}", e);
}
}
});
}
public static boolean execCommond(String... args) {
boolean flg = true;
Runtime run = Runtime.getRuntime();
try {
Process p;
if (args != null && args.length == 1) {
p = run.exec(args[0]);
} else {
p = run.exec(args);
}
InputStream stream=p.getInputStream();
LoggerUtils.info(CmdExecUtils.class, stream + "....getInputStream..");
//消費正常日志
clearStream(stream);
//消費錯誤日志
clearStream(p.getErrorStream());
if (p.waitFor() != 0) {
if (p.exitValue() == 1) {
LoggerUtils.info(CmdExecUtils.class,"=============exec=====================命令執行失敗!");
flg = false;
}
}
} catch (Exception e) {
e.printStackTrace();
flg = false;
}
return flg;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/216594.html
標籤:其他
