最近由于在编码中需要在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直接执行该脚本。