前言
最近在开发一个系统时,需求是Java调用Python脚本,这里我使用 Process process = Runtime.getRuntime().exec() 来调用,脚本用命令行能完整运行,但用Java调却一直转圈圈,等很久也不见结束.文章为记录…
参考文章
1.使用process调用py脚本
public static ResultVO pyInvoke(String[] arguments) throws Exception {
Process process = Runtime.getRuntime().exec(arguments);
/**
* GBK是防止Python输出乱码
*/
try {
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
//java代码中的process.waitFor()返回值为0表示我们调用python脚本成功,
//返回值为1表示调用python脚本失败,这和我们通常意义上见到的0与1定义正好相反
int re = process.waitFor();
in.close();
if (re == 1) {
log.info("调用脚本失败");
return ResultVoUtil.error("调用失败");
} else {
log.info("调用脚本成功");
return ResultVoUtil.success("调用成功");
}
} catch (Exception e) {
e.printStackTrace();
}
return ResultVoUtil.success();
}
首先我们先使用 Process 创建出该对象,该对象我这里暂时使用了第一个参数为python3(通过数组传),第二个参数为脚本地址。后边主要就是打印 输入流,获取py脚本。其实到这里java 调用py脚本 就已经完 了,但是后续开发中遇到一种问题,就是程序莫名死锁,没有响应,于是使用debug 跟进代码,发现程序走到 waitfor 代码行的时候程序就出现了挂起的情况,于是google了一番,明白了其中的原因。
2. waitfor 问题描述分析
1.主进程中调用Runtime.getRuntime().exec() 会创建一个子进程,用于执行python脚本。子进程创建后会和主进程分别独立运行。
2.因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。
3.子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。
4.这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
5.这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。
3.死锁问题的解决
基于上述分析,只要主进程在waitfor之前,能不断处理缓冲区中的数据就可以。因为,我们可以再waitfor之前,单独启两个额外的线程,分别用于处理InputStream和ErrorStream就可以解决.
try {
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
//获取进程的标准输入流
final InputStream is1 = process.getInputStream();
//获取进城的错误流
final InputStream is2 = process.getErrorStream();
//启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
new Thread() {
public void run() {
BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
try {
String line1 = null;
while ((line1 = br1.readLine()) != null) {
if (line1 != null){}
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
try {
is1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
try {
String line2 = null ;
while ((line2 = br2.readLine()) != null ) {
if (line2 != null){}
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
try {
is2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
//可能导致进程阻塞,甚至死锁
int ret = process.waitFor();
in.close();
if (re == 1) {
log.info("调用脚本失败");
return ResultVoUtil.error("调用失败");
} else {
log.info("调用脚本成功");
return ResultVoUtil.success("调用成功");
}
}catch (Exception ex){
ex.printStackTrace();
try{
process.getErrorStream().close();
process.getInputStream().close();
process.getOutputStream().close();
}
catch(Exception ee){}
}
如此便可以将 waitfor死锁问题避开,看完这个问题,总结一下,多看官方api注释…其实官方已经提示我们,如下 为 api注释
Causes the current thread to wait, if necessary, until the
* process represented by this {@code Process} object has
* terminated. This method returns immediately if the subprocess
* has already terminated. If the subprocess has not yet
* terminated, the calling thread will be blocked until the
* subprocess exits.
@return the exit value of the subprocess represented by this
* {@code Process} object. By convention, the value
* {@code 0} indicates normal termination.
* @throws InterruptedException if the current thread is
* {@linkplain Thread#interrupt() interrupted} by another
* thread while it is waiting, then the wait is ended and
* an {@link InterruptedException} is thrown.
如果需要,导致当前线程等待,直到此{@code Process}对象表示的进程具有终止 如果子进程,此方法立即返回已经终止。 如果子进程还没有终止后,调用线程将被阻塞,直到子进程退出。