最近由于在编码中需要在java代码中执行linux命令,使用到了Runtime类的一些方法,也出现几个小bug,所以趁这个机会对Runtime也就是运行时环境这个类进行总结。
Runtime.getRuntime()能得到一个Runtime对象实例,也就是当前运行时环境实例,这个玩艺是什么东西?java中称为虚拟机的运行时环境,这个说法很抽象,我在网上百度了很久,没有确切的说法,我感觉这个Runtime对象实例其实更像是java 程序的进程的概念,当然只是我初步的想法,后续找到更多资料可能就发现错了。
1.去看下Runtime的源码,首先可以发现Runtime实用了单实例的设计模式
private static Runtime currentRuntime = newRuntime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
--上面就是典型的单实例设计模式,所以在java程序中不同线程通过调用Runtime.getRuntime()获得的是同一个对象实例,也就是说一个java进程中只有一个Runtime实例
2. Runtime的一些方法
主要使用的方法就是exec,在Runtime中,exec方法进行了多次重载
public Process exec(String command) throwsIOException
public Process exec(String command,String[] envp)
public Process exec(String command,String[] envp, File dir)
public Process exec(String cmdarray[])throws IOException
比较多用到的是第一个和第四个,第三个有两个参数String[] envp, File dir,这是什么?前者是指环境变量字符串,后者是指工作目录。这一点跟进程很相似。不指定这两个参数时默认是当前环境和当前目录。再看看这个方法的返回值是Process类型,这个是什么?其实是子进程的一个管理器实例,也就是说我们在调用exec方法时,实际上是创建了一个子进程,并且由该子进程执行我们传入的命令。这点可以明显从exec源码看出
public Process exec(String[] cmdarray,String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
-- ProcessBuilder创建子进程,并设置环境变量和工作目录,然后start。可见父进程子进程的概念在java中还是存在的。
3. Process的主要方法
destroy()
杀掉子进程。
exitValue()
返回子进程的出口值。
InputStream getErrorStream()
获得子进程的错误流。
InputStream getInputStream()
获得子进程的输入流。
OutputStream getOutputStream()
获得子进程的输出流。
waitFor()
导致当前线程等待,如果必要,一直要等到由该 Process 对象表示的进程已经终止。
--一般执行linux命令使用比较多的是waitFor(),诸塞等待子进程完成该linux命令。然后再继续线程后续的代码。
--以上基本简单把Runtime的应用机制介绍了下,虽然不算深入,但是平常简单的调用linux命令这种简单的工作就能做到基本心里有底了,下面继续说说在应用过程中遇到的小坑
坑一:如何在exec执行多条linux命令
不熟悉时可能会把一段linux命令当作输入直接执行,然后发现并没有执行,这是为什么呢?比如”mv dir1/*.* dir2;wait;gzip dir2/*.*;” 这段命令直接在linux上直接执行是没问题的,但是放到exec执行会发现根本没能执行,原因是exec最终会对command进行一次字符串分割,默认以” \t\n\r\f”(前有一个空格,引号不是)为分割符,上面语句不符合格式。
public Process exec(String command,String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
}
正确用法是用&&连接起来”mv dir1/*.* dir2&&wait&&gzip dir2/*.*”,或则把三个命令加入String数组,调用publicProcess exec(String cmdarray[]) throws IOException这个重载方法。
坑二:为什么如果在java代码中不调用waitFor()阻塞等待linux中命令执行结束会出现部分命令并没有执行的情况。
比如我遇到的问题,在java中执行了下面两行代码
Runtime.getRuntime().exec(“mv dir1/file.txtdir2”);
Runtime.getRuntime().exec(“gzip dir2/ file.txt”);
多线程执行一段时间后会发现有部分文件没有按希望去压缩,这是什么原因,百思不得其解,目前还没搞得太清楚,初步认为是主进程创建了两个子进程,如果不诸塞的话,可能当进程1的文件还没挪到dir2,进程2已经执行了,所以这部分文件没有按希望进行压缩。但是为什么进程二没有报文件不存在的错误呢?这个也是跟疑问。不管怎么说解决之道就是加阻塞。
Runtime.getRuntime().exec(“mv dir1/file.txtdir2”) .waitFor();
Runtime.getRuntime().exec(“gzip dir2/ file.txt”).waitFor();
加上.waitFor()后,发现之前的问题解决了,文件全部都进行了压缩。
由于现在的网络不好,很多测试不能上传到linux主机上执行,后续有机会测试后再继续补充。
--补充修正
如何在exec执行多条linux命令
调用public Process exec(String cmdarray[]) throws IOException这个重载方法是无法执行多条命令的,数组的作用只是让你手工把命令和传入参数切割开来,以便后续子进程的调用,另外”mv dir1/*.* dir2&&wait&&gzip dir2/*.*”这种用&&连接的多命令也是不可用的,第调用多条命令只能一个个命令执行,或则把多条命令写入脚本,exec直接执行该脚本。