Java执行Cmd命令死锁
Stars-one 人气:0问题
之前研究了Java通过执行cmd命令从而触发Android打包的思路,但是发现Android打包成功之后,后面的代码逻辑就不走了(连输出都没有)
经过了一天的排查,终于是从网上找到了解决方法
原因及解决方法
原因分析: 在上面提及了, process创建的子进程没有自己的控制台或终端,其所有的io操作都是通过(输入流、输出流、错误流)重定向到父进程中
如果该可执行程序的输入、输出或者错误输出比较多的话,而由于运行窗口的标准输入、输出等缓冲区有大小的限制,则可能导致子进程阻塞,甚至产生死锁
其解决方法就是在waitfor()
方法之前读出窗口的标准输出、输出、错误缓冲区中的内容。
方法封装
下面代码中的TeeInputStream是在lang3包依赖中,记得添加依赖
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
Java版本:
/** * 执行命令行,并等待命令执行完毕,同时将过程中的控制台输出日志写入日志文件中 * @param cmd 命令,window记得要使用cmd /c开头,如cmd /c ipconfig * @param dir 命令行所在路径 * @param logFile 日志文件 * @throws IOException * @throws InterruptedException */ private void execCmdLine(String cmd, File dir, File logFile) throws IOException, InterruptedException { Process process = Runtime.getRuntime().exec(cmd, null, dir); InputStream inputStream = process.getInputStream(); //开启两个线程用来读取流,否则会造成死锁问题 new Thread(() -> { FileOutputStream fileOutputStream = null; TeeInputStream teeInputStream = null; BufferedReader bufferedReader = null; try { fileOutputStream = new FileOutputStream(logFile, true); //使用分流器,输出日志文件 teeInputStream = new TeeInputStream(inputStream, fileOutputStream); //这里gbk格式需要注意,我是在window上测试的,所以使用是gbk方式,如果是其他平台,可能需要使用utf-8格式 bufferedReader = new BufferedReader(new InputStreamReader(teeInputStream, "gbk")); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { bufferedReader.close(); teeInputStream.close(); fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { InputStreamReader err = new InputStreamReader(process.getErrorStream()); BufferedReader bferr = new BufferedReader(err); String errline = ""; try { while ((errline = bferr.readLine()) != null) { System.out.println("流错误:" + errline); } } catch (Exception e) { e.printStackTrace(); } finally { try { bferr.close(); err.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); process.waitFor(); process.destroy(); }
Kotlin版本:
/** * 执行命令行,并等待命令执行完毕,同时将过程中的控制台输出日志写入日志文件中 * - [cmd] 命令,window记得要使用cmd /c开头,如cmd /c ipconfig * - [dir] 命令行所在路径 * - [logFile] 日志文件 */ fun execCmd(cmd: String, dir: File, logFile: File) { val process = Runtime.getRuntime().exec(cmd, null, dir) val inputStream = process.inputStream //开启两个线程用来读取流,否则会造成死锁问题 thread { var fileOutputStream: FileOutputStream? = null var teeInputStream: TeeInputStream? = null var bufferedReader: BufferedReader? = null try { fileOutputStream = FileOutputStream(logFile, true) //使用分流器,日志文件和 teeInputStream = TeeInputStream(inputStream, fileOutputStream) //区分不同平台 bufferedReader = if (isWin()) { BufferedReader(InputStreamReader(teeInputStream, "gbk")) } else { BufferedReader(InputStreamReader(teeInputStream, "utf-8")) } var line: String? while (bufferedReader.readLine().also { line = it } != null) { println(line) } } catch (e: IOException) { e.printStackTrace() } finally { try { bufferedReader!!.close() teeInputStream!!.close() fileOutputStream!!.close() } catch (e: IOException) { e.printStackTrace() } } } thread { val err = InputStreamReader(process.errorStream) val bferr = BufferedReader(err) var errline = "" try { while (bferr.readLine().also { errline = it } != null) { println("流错误:$errline") } } catch (e: Exception) { e.printStackTrace() } finally { try { bferr.close() err.close() } catch (e: IOException) { e.printStackTrace() } } } process.waitFor() process.destroy() }
代码封装在库中stars-one/common-controls: TornadoFx的常用控件 controls for tornadofx
参考
加载全部内容