场景:
java在企业级项目开发中,需要调用exe、shell这样的程序或脚本。
在Java中提供了两种方法来启动其他程序:
(1) 使用Runtime的exec()方法
(2) 使用ProcessBuilder的start()方法 。
Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。
此次分析第一种方法。
注意:
在Java中,调用runtime线程执行脚本是非常消耗资源的,所以建议不要频繁使用!
关于waitFor() 阻塞/锁死 问题:
JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入、标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容
java简单工具类代码:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import java.io.*;
@Slf4j
public class CommandUtil {
/**
* 执行 inux/cmd 命令
*
* @param command 命令
* @return
*/
public static boolean executeCommand(String command) {
return executeCommand(command, null);
}
/**
* 指定目录执行 linux/cmd 命令
*
* @param command 命令
* @param file 目录
* @return
*/
public static boolean executeCommand(String command, File file) {
BufferedReader reader = null;
InputStreamReader inputStreamReader = null;
Process p;
try {
//判断是操作系统是linux还是windows
String[] comds;
if (System.getProperties().get("os.name").toString().toUpperCase().indexOf("WINDOWS") >= 0) {
log.info("当前操作系统:windows");
comds = new String[]{"cmd", "/c", command};
} else {
log.info("当前操作系统:linux");
comds = new String[]{"/bin/sh", "-c", command};
}
// 开始执行命令
log.info("执行命令:" + command);
if (file!=null) {
p = Runtime.getRuntime().exec(comds, null, file);
} else {
p = Runtime.getRuntime().exec(comds);
}
//开启线程监听(此处解决 waitFor() 阻塞/锁死 问题)
new RunThread(p.getInputStream(), "INFO").start();
new RunThread(p.getErrorStream(), "ERROR").start();
int flag = p.waitFor();
return true;
} catch (IOException e) {
log.error("Proto2Html 执行命令IO异常!", e);
return false;
} catch (InterruptedException e) {
log.error("Proto2Html 执行命令中断异常!", e);
return false;
} catch (Exception e) {
log.error("Proto2Html 执行命令出现异常!", e);
return false;
} finally {
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(inputStreamReader);
}
}
}
/**
* 监听线程
*/
@Slf4j
class RunThread extends Thread {
InputStream is;
String printType;
RunThread(InputStream is, String printType) {
this.is = is;
this.printType = printType;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is, "GBK");
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine())!=null){
log.info(printType + ">" + line);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
测试案例:
public static void main(String[] args) {
//进入demo目录执行 maven 打包命令
File file = new File("d:/workRepository/demo");
String command = "mvn clean package";
CommandUtil.executeCommand(command,file);
}
RunTime.getRuntime().exec() 用法:
// 在单独的进程中执行指定的字符串命令
public Process exec(String command);
// 在单独的进程中执行指定命令和变量
public Process exec(String[] cmdArray);
// 在指定环境的独立进程中执行指定命令和变量
public Process exec(String command,String[] envp);
// 在指定环境的独立进程中执行指定命令和变量
public Process exec(String[] cmdArray,String[] envp);
// 在有指定的环境和工作目录的独立进程中执行指定的字符串命令
public Process exec(String command,String[] encp,File dir);
// 在指定环境和工作目录的独立进程中执行指定的命令和变量
public Process exec(String[] cmdarray,String[] envp,File dir);
深入:
Process的几种方法
public static void main(String[] args) {
Process p = Runtime.getRuntime().exec(comds);
//1、杀掉子进程
p.destroy();
//2、返回子进程的出口值,值0表示正常终止
p.exitValue();
//3、获取子进程的错误流
p.getErrorStream();
//4、获取子进程的输入流
p.getInputStream();
//5、获取子进程的输出流
p.getOutputStream();
/*6、导致当前线程等待,如有必要,一直要等到由该Process对象表示的进程已经终止。
如果已终止该子进程,此方法立即返回。如果没有终止该子进程,
调用的线程将被阻塞,直到退出子进程,根据管理,0表示正常终止。*/
p.waitFor();
}