`
ynztpwl
  • 浏览: 55509 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Runtime.exec的使用好文章

 
阅读更多

一、英文原地址
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

二、差不多意思的英文翻译
那就首先说点Runtime类吧,他是一个与JVM运行时环境有关的类,这个类是Singleton的。我说几个自己觉得重要的地方。

1、Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一一个得到运行时环境的方法。

2、Runtime上其他大部分的方法都是实例方法,也就是说每次进行运行时调用时都要用到getRuntime方法。

3、Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的一个吧,因为我看到System类中的exit实际上也是通过调用Runtime.exit()来退出JVM的,这里说明一下Java对Runtime返回值的一般规则(后边也提到了),0代表正常退出,非0代表异常中止,这只是Java的规则,在各个操作系统中总会发生一些小的混淆。



4、Runtime.addShutdownHook()方法可以注册一个hook在JVM执行shutdown的过程中,方法的参数只要是一个初始化过但是没有执行的Thread实例就可以。(注意,Java中的Thread都是执行过了就不值钱的哦)

5、说到addShutdownHook这个方法就要说一下JVM运行环境是在什么情况下shutdown或者abort的。文档上是这样写的,当最后一个非精灵进程退出或者收到了一个用户中断信号、用户登出、系统shutdown、Runtime的exit方法被调用时JVM会启动shutdown的过程,在这个过程开始后,他会并行启动所有登记的shutdown hook(注意是并行启动,这就需要线程安全和防止死锁)。当shutdown过程启动后,只有通过调用halt方法才能中止shutdown的过程并退出JVM。

那什么时候JVM会abort退出那?首先说明一下,abort退出时JVM就是停止运行但并不一定进行shutdown。这只有JVM在遇到SIGKILL信号或者windows中止进程的信号、本地方法发生类似于访问非法地址一类的内部错误时会出现。这种情况下并不能保证shutdown hook是否被执行。


现在开始看这篇文章,呵呵。


首先讲的是Runtime.exec()方法的所有重载。这里要注意的有一点,就是public Process exec(String [] cmdArray, String [] envp);这个方法中cmdArray是一个执行的命令和参数的字符串数组,数组的第一个元素是要执行的命令往后依次都是命令的参数,envp我个人感觉应该和C中的execve中的环境变量是一样的,envp中使用的是name=value的方式。


1、 一个很糟糕的调用程序,代码如下,这个程序用exec调用了一个外部命令之后马上使用exitValue就对其返回值进行检查,让我们看看会出现什么问题。

import java.util.*;
import java.io.*;

public class BadExecJavac
{
public static void main(String args[])
{
try
{ 
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
int exitVal = proc.exitValue();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


A run of BadExecJavac produces:


E:classescomjavaworldjpitfallsarticle2>java BadExecJavac
java.lang.IllegalThreadStateException: process has not exited
at java.lang.Win32Process.exitValue(Native Method)
at BadExecJavac.main(BadExecJavac.java:13)


这里看原文就可以了解,这里主要的问题就是错误的调用了exitValue来取得外部命令的返回值(呵呵,这个错误我也曾经犯过),因为exitValue这个方法是不阻塞的,程序在调用这个方法时外部命令并没有返回所以造成了异常的出现,这里是由另外的方法来等待外部命令执行完毕的,就是waitFor方法,这个方法会一直阻塞直到外部命令执行结束,然后返回外部命令执行的结果,作者在这里一顿批评设计者的思路有问题,呵呵,反正我是无所谓阿,能用就可以拉。但是作者在这里有一个说明,就是exitValue也是有好多用途的。因为当你在一个Process上调用waitFor方法时,当前线程是阻塞的,如果外部命令无法执行结束,那么你的线程就会一直阻塞下去,这种意外会影响我们程序的执行。所以在我们不能判断外部命令什么时候执行完毕而我们的程序还需要继续执行的情况下,我们就应该循环的使用exitValue来取得外部命令的返回状态,并在外部命令返回时作出相应的处理。


2、对exitValue处改进了的程序

import java.util.*;
import java.io.*;

public class BadExecJavac2
{
public static void main(String args[])
{
try
{ 
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


不幸的是,这个程序也无法执行完成,它没有输出但却一直悬在那里,这是为什么那?


JDK文档中对此有如此的解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的锁,甚至死锁。


文档引述完了,作者又开始批评了,他说JDK仅仅说明为什么问题会发生,却并没有说明这个问题怎么解决,这的确是个问题哈。紧接着作者说出自己的做法,就是在执行完外部命令后我们要控制好Process的所有输入和输出(视情况而定),在这个例子里边因为调用的是Javac,而他在没有参数的情况下会将提示信息输出到标准出错,所以在下面的程序中我们要对此进行处理。

import java.util.*;
import java.io.*;

public class MediocreExecJavac
{
public static void main(String args[])
{
try
{ 
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("javac");
InputStream stderr = proc.getErrorStream();
InputStreamReader isr = new InputStreamReader(stderr);
BufferedReader br = new BufferedReader(isr);
String line = null;
System.out.println("<error></error>");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


程序的运行结果为

E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac
<error></error>
Usage: javac <options></options> <source files=""></source>

where <options></options> includes:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-O Optimize; may hinder debugging or enlarge class files
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath Specify where to find user class files
-sourcepath Specify where to find input source files
-bootclasspath Override location of bootstrap class files
-extdirs <dirs></dirs>Override location of installed extensions
-d <directory></directory>Specify where to place generated class files
-encoding <encoding></encoding>Specify character encoding used by source files
-target <release></release>Generate class files for specific VM version

Process exitValue: 2


哎,不管怎么说还是出来了结果,作者作了一下总结,就是说,为了处理好外部命令大量输出的情况,你要确保你的程序处理好外部命令所需要的输入或者输出。


下一个题目,当我们调用一个我们认为是可执行程序的时候容易发生的错误(今天晚上我刚刚犯这个错误,没事做这个练习时候发生的)

import java.util.*;
import java.io.*;

public class BadExecWinDir
{
public static void main(String args[])
{
try
{ 
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("dir");
InputStream stdin = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdin);
BufferedReader br = new BufferedReader(isr);
String line = null;
System.out.println("<output></output>");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("");
int exitVal = proc.waitFor(); 
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


A run of BadExecWinDir produces:


E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
java.io.IOException: CreateProcess: dir error=2
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at BadExecWinDir.main(BadExecWinDir.java:12)


说实在的,这个错误还真是让我摸不着头脑,我觉得在windows中返回2应该是没有找到这个文件的缘故,可能windows 2000中只有cmd命令,dir命令不是当前环境变量能够解释的吧。我也不知道了,慢慢往下看吧。

嘿,果然和作者想的一样,就是因为dir命令是由windows中的解释器解释的,直接执行dir时无法找到dir.exe这个命令,所以会出现文件未找到这个2的错误。如果我们要执行这样的命令,就要先根据操作系统的不同执行不同的解释程序command.com 或者cmd.exe。

作者对上边的程序进行了修改

import java.util.*;
import java.io.*;

class StreamGobbler extends Thread
{
InputStream is;
String type;

StreamGobbler(InputStream is, String type)
{
this.is = is;
this.type = type;
}

public void run()
{
try
{
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
System.out.println(type + ">" + line); 
} catch (IOException ioe)
{
ioe.printStackTrace(); 
}
}
}

public class GoodWindowsExec
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE: java GoodWindowsExec <cmd></cmd>");
System.exit(1);
}

try
{ 
String osName = System.getProperty("os.name" );
String[] cmd = new String[3];

if( osName.equals( "Windows NT" ) )
{
cmd[0] = "cmd.exe" ;
cmd[1] = "/C" ;
cmd[2] = args[0];
}
else if( osName.equals( "Windows 95" ) )
{
cmd[0] = "command.com" ;
cmd[1] = "/C" ;
cmd[2] = args[0];
}

Runtime rt = Runtime.getRuntime();
System.out.println("Execing " + cmd[0] + " " + cmd[1] 
+ " " + cmd[2]);
Process proc = rt.exec(cmd);
// any error message?
StreamGobbler errorGobbler = new 
StreamGobbler(proc.getErrorStream(), "ERROR"); 

// any output?
StreamGobbler outputGobbler = new 
StreamGobbler(proc.getInputStream(), "OUTPUT");

// kick them off
errorGobbler.start();
outputGobbler.start();

// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal); 
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


Running GoodWindowsExec with the dir command generates:


E:classescomjavaworldjpitfallsarticle2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:classescomjavaworldjpitfallsarticle2
OUTPUT>
OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java
OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java
OUTPUT>10/24/00 08:45p 488 BadExecJavac.java
OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java
OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java
OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java
OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00 09:29p 151 SuperFrame.java
OUTPUT>10/24/00 09:23p 1,814 TestExec.java
OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java
OUTPUT>10/12/00 08:55p 228 TopLevel.java
OUTPUT> 22 File(s) 46,661 bytes
OUTPUT> 19,678,420,992 bytes free
ExitValue: 0

这里作者教了一个windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一个windows中注册了后缀的文档名,windows会自动地调用相关的程序来打开这个文档,我试了一下,的确很好用,但是好像文件路径中有空格的话就有点问题,我加上引号也无法解决。

这里作者强调了一下,不要假设你执行的程序是可执行的程序,要清楚自己的程序是单独可执行的还是被解释的,本章的结束作者会介绍一个命令行工具来帮助我们分析。

这里还有一点,就是得到process的输出的方式是getInputStream,这是因为我们要从Java 程序的角度来看,外部程序的输出对于Java来说就是输入,反之亦然。


最后的一个漏洞的地方就是错误的认为exec方法会接受所有你在命令行或者Shell中输入并接受的字符串。这些错误主要出现在命令作为参数的情况下,程序员错误的将所有命令行中可以输入的参数命令加入到exec中(这段翻译的不好,凑合看吧)。下面的例子中就是一个程序员想重定向一个命令的输出。

import java.util.*;
import java.io.*;

// StreamGobbler omitted for brevity

public class BadWinRedirect
{
public static void main(String args[])
{
try
{ 
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("java jecho 'Hello World' > test.txt");
// any error message?
StreamGobbler errorGobbler = new 
StreamGobbler(proc.getErrorStream(), "ERROR"); 

// any output?
StreamGobbler outputGobbler = new 
StreamGobbler(proc.getInputStream(), "OUTPUT");

// kick them off
errorGobbler.start();
outputGobbler.start();

// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal); 
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


Running BadWinRedirect produces:


E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0

程序员的本意是将Hello World这个输入重订向到一个文本文件中,但是这个文件并没有生成,jecho仅仅是将命令行中的参数输出到标准输出中,用户觉得可以像dos中重定向一样将输出重定向到一个文件中,但这并不能实现,用户错误的将exec认为是一个shell解释器,但它并不是,如果你想将一个程序的输出重定向到其他的程序中,你必须用程序来实现他。可用java.io中的包。

import java.util.*;
import java.io.*;

class StreamGobbler extends Thread
{
InputStream is;
String type;
OutputStream os;

StreamGobbler(InputStream is, String type)
{
this(is, type, null);
}

StreamGobbler(InputStream is, String type, OutputStream redirect)
{
this.is = is;
this.type = type;
this.os = redirect;
}

public void run()
{
try
{
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);

InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line); 
}
if (pw != null)
pw.flush();
} catch (IOException ioe)
{
ioe.printStackTrace(); 
}
}
}

public class GoodWinRedirect
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE java GoodWinRedirect <outputfile></outputfile>");
System.exit(1);
}

try
{ 
FileOutputStream fos = new FileOutputStream(args[0]);
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("java jecho 'Hello World'");
// any error message?
StreamGobbler errorGobbler = new 
StreamGobbler(proc.getErrorStream(), "ERROR"); 

// any output?
StreamGobbler outputGobbler = new 
StreamGobbler(proc.getInputStream(), "OUTPUT", fos);

// kick them off
errorGobbler.start();
outputGobbler.start();

// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
fos.flush();
fos.close(); 
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


Running GoodWinRedirect produces:


E:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
OUTPUT>'Hello World'
ExitValue: 0

这里就不多说了,看看就明白,紧接着作者给出了一个监测命令的小程序

import java.util.*;
import java.io.*;

// class StreamGobbler omitted for brevity

public class TestExec
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE: java TestExec "cmd"");
System.exit(1);
}

try
{
String cmd = args[0];
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);

// any error message?
StreamGobbler errorGobbler = new 
StreamGobbler(proc.getErrorStream(), "ERR"); 

// any output?
StreamGobbler outputGobbler = new 
StreamGobbler(proc.getInputStream(), "OUT");

// kick them off
errorGobbler.start();
outputGobbler.start();

// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


对这个程序进行运行:
E:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
java.io.IOException: CreateProcess: e:javadocsindex.html error=193
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at TestExec.main(TestExec.java:45)

193在windows中是说这不是一个win32程序,这说明路径中找不到这个网页的关联程序,下面作者决定用一个绝对路径来试一下。

E:classescomjavaworldjpitfallsarticle2>java TestExec
"e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
ExitValue: 0


好用了,这个我也试了一下,用的是IE。


最后,作者总结了几条规则,防止我们在进行Runtime.exec()调用时出现错误。


1、 在一个外部进程执行完之前你不能得到他的退出状态

2、 在你的外部程序开始执行的时候你必须马上控制输入、输出、出错这些流。

3、 你必须用Runtime.exec()去执行程序

4、 你不能象命令行一样使用Runtime.exec()。

三、另一篇讲到Runtime.exec相关的
在编写Java程序时,有时候需要在Java程序中执行另外一个程序。

  1、启动程序Java提供了两种方法用来启动其它程序:

  (1)使用Runtime的exec()方法

  (2)使用ProcessBuilder的start()方法

  不管在哪种操作系统下,程序具有基本类似的一些属性。一个程序启动后就程序操作系统的一个进程,进程在执行的时候有自己的环境变量、有自己的工作目录。Runtime和ProcessBuilder提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。

  能够在Java中执行的外部程序,必须是一个实际存在的可执行文件,对于shell下的内嵌命令是不能直接执行的。

  采用Runtime的exec执行程序时,首先要使用Runtime的静态方法得到一个Runtime,然后调用Runtime的exec方法。可以将要执行的外部程序和启动参数、环境变量、工作目录作为参数传递给exec方法,该方法执行后返回一个Process代表所执行的程序。

  Runtime有六个exec方法,其中两个的定义为:

  public Process exec(String[] cmdarray, String[] envp, File dir)

  public Process exec(String command, String[] envp, File dir)

  cmdarray和command为要执行的命令,可以将命令和参数作为一个字符串command传递给exec()方法,也可以将命令和参数一个一个的方在数组cmdarray里传递给exec()方法。

  envp为环境变量,以name=value的形式放在数组中。dir为工作目录。

  可以不要dir参数,或者不要envp和dir参数,这样就多出了其它4个exec()方法。如果没有dir参数或者为null,那么新启动的进程就继承当前java进程的工作目录。如果没有envp参数或者为null,那么新启动的进程就继承当前java进程的环境变量。

  也可以使用ProcessBuilder类启动一个新的程序,该类是后来添加到JDK中的,而且被推荐使用。通过构造函数设置要执行的命令以及参数,或者也可以通过command()方法获取命令信息后在进行设置。通过directory(File directory)方法设置工作目录,通过environment()获取环境变量信息来修改环境变量。

  在使用ProcessBuilder构造函数创建一个新实例,设置环境变量、工作目录后,可以通过start()方法来启动新程序,与Runtime的exec()方法一样,该方法返回一个Process对象代表启动的程序。

  ProcessBuilder与Runtime.exec()方法的不同在于ProcessBuilder提供了 redirectErrorStream(boolean redirectErrorStream)方法,该方法用来将进程的错误输出重定向到标准输出里。即可以将错误输出都将与标准输出合并。

  2、Process

  不管通过那种方法启动进程后,都会返回一个Process类的实例代表启动的进程,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法:

  (1) void destroy()

  杀掉子进程。

  一般情况下,该方法并不能杀掉已经启动的进程,不用为好。

  (2) int exitValue()

  返回子进程的出口值。

  只有启动的进程执行完成、或者由于异常退出后,exitValue()方法才会有正常的返回值,否则抛出异常。

  (3)InputStream getErrorStream()

  获取子进程的错误流。

  如果错误输出被重定向,则不能从该流中读取错误输出。

  (4)InputStream getInputStream()

  获取子进程的输入流。

  可以从该流中读取进程的标准输出。

  (5)OutputStream getOutputStream()

  获取子进程的输出流。

  写入到该流中的数据作为进程的标准输入。

  (6) int waitFor()

  导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。

  通过该类提供的方法,可以实现与启动的进程之间通信,达到交互的目的。

  3、从标准输出和错误输出流读取信息

  从启动其他程序的Java进程看,已启动的其他程序输出就是一个普通的输入流,可以通过getInputStream()和getErrorStream来获取。

  对于一般输出文本的进程来说,可以将InputStream封装成BufferedReader,然后就可以一行一行的对进程的标准输出进行处理。

  4、举例

  (1)Runtime.exec()

import java.io.BufferedReader;

import java.io.File;

import java.io.InputStreamReader;

public class Test1 {

	public static void main(String[] args) {

		try {

			Process p = null;

			String line = null;

			BufferedReader stdout = null;

			// list the files and directorys under C:\

			p = Runtime.getRuntime().exec("CMD.exe /C dir", null,
					new File("C:\\"));

			stdout = new BufferedReader(new InputStreamReader(p

			.getInputStream()));

			while ((line = stdout.readLine()) != null) {

				System.out.println(line);

			}

			stdout.close();

			// echo the value of NAME

			p = Runtime.getRuntime().exec("CMD.exe /C echo %NAME%",
					new String[] { "NAME=TEST" });

			stdout = new BufferedReader(new InputStreamReader(p

			.getInputStream()));

			while ((line = stdout.readLine()) != null) {

				System.out.println(line);

			}

			stdout.close();

		} catch (Exception e) {

			e.printStackTrace();

		}

	}

}



(2)ProcessBuilder

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test2 {
	public static void main(String[] args) {
		try {
			List list = new ArrayList();
			ProcessBuilder pb = null;
			Process p = null;
			String line = null;
			BufferedReader stdout = null;// list the files and directorys
											// under C:\
			list.add("CMD.EXE");
			list.add("/C");
			list.add("dir");
			pb = new ProcessBuilder(list);
			pb.directory(new File("C:\\"));
			p = pb.start();
			stdout = new BufferedReader(new InputStreamReader(p
					.getInputStream()));
			while ((line = stdout.readLine()) != null) {
				System.out.println(line);
			}
			stdout.close();// echo the value of NAME
			pb = new ProcessBuilder();
			pb.command(new String[] { "CMD.exe", "/C", "echo %NAME%" });
			pb.environment().put("NAME", "TEST");
			p = pb.start();
			stdout = new BufferedReader(new InputStreamReader(p
					.getInputStream()));
			while ((line = stdout.readLine()) != null) {
				System.out.println(line);
			}
			stdout.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



5、获取进程的返回值

  通常,一个程序/进程在执行结束后会向操作系统返回一个整数值,0一般代表执行成功,非0表示执行出现问题。有两种方式可以用来获取进程的返回值。一是利用waitFor(),该方法是阻塞的,执导进程执行完成后再返回。该方法返回一个代表进程返回值的整数值。另一个方法是调用 exitValue()方法,该方法是非阻塞的,调用立即返回。但是如果进程没有执行完成,则抛出异常。

  6、阻塞的问题

  由Process代表的进程在某些平台上有时候并不能很好的工作,特别是在对代表进程的标准输入流、输出流和错误输出进行操作时,如果使用不慎,有可能导致进程阻塞,甚至死锁。

  如果将以上事例中的从标准输出重读取信息的语句修改为从错误输出流中读取:

  stdout = new BufferedReader(new InputStreamReader(p.getErrorStream()));

  那么程序将发生阻塞,不能执行完成,而是hang在那里。

  当进程启动后,就会打开标准输出流和错误输出流准备输出,当进程结束时,就会关闭他们。在以上例子中,错误输出流没有数据要输出,标准输出流中有数据输出。由于标准输出流中的数据没有被读取,进程就不会结束,错误输出流也就不会被关闭,因此在调用readLine()方法时,整个程序就会被阻塞。为了解决这个问题,可以根据输出的实际先后,先读取标准输出流,然后读取错误输出流。

  但是,很多时候不能很明确的知道输出的先后,特别是要操作标准输入的时候,情况就会更为复杂。这时候可以采用线程来对标准输出、错误输出和标准输入进行分别处理,根据他们之间在业务逻辑上的关系决定读取那个流或者写入数据。

  针对标准输出流和错误输出流所造成的问题,可以使用ProcessBuilder的redirectErrorStream()方法将他们合二为一,这时候只要读取标准输出的数据就可以了。

  当在程序中使用Process的waitFor()方法时,特别是在读取之前调用waitFor()方法时,也有可能造成阻塞。可以用线程的方法来解决这个问题,也可以在读取数据后,调用waitFor()方法等待程序结束。

  总之,解决阻塞的方法应该有两种:

  (1)使用ProcessBuilder类,利用redirectErrorStream方法将标准输出流和错误输出流合二为一,在用start()方法启动进程后,先从标准输出中读取数据,然后调用waitFor()方法等待进程结束。

  如:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test3 {
	public static void main(String[] args) {
		try {
			List list = new ArrayList();
			ProcessBuilder pb = null;
			Process p = null;
			String line = null;
			BufferedReader stdout = null;
			// list the files and directorys under C:\
			list.add("CMD.EXE");
			list.add("/C");
			list.add("dir");
			pb = new ProcessBuilder(list);
			pb.directory(new File("C:\\"));
			// merge the error output with the standard output
			pb.redirectErrorStream(true);
			p = pb.start();
			// read the standard output
			stdout = new BufferedReader(new InputStreamReader(p
					.getInputStream()));
			while ((line = stdout.readLine()) != null) {
				System.out.println(line);
			}
			int ret = p.waitFor();
			System.out.println("the return code is " + ret);
			stdout.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



(2)使用线程

import java.util.*;
import java.io.*;

class StreamWatch extends Thread {
	InputStream is;
	String type;
	List output = new ArrayList();
	boolean debug = false;

	StreamWatch(InputStream is, String type) {
		this(is, type, false);
	}

	StreamWatch(InputStream is, String type, boolean debug) {
		this.is = is;
		this.type = type;
		this.debug = debug;
	}

	public void run() {
		try {
			PrintWriter pw = null;
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line = null;
			while ((line = br.readLine()) != null) {
				output.add(line);
				if (debug)
					System.out.println(type + ">" + line);
			}
			if (pw != null)
				pw.flush();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	public List getOutput() {
		return output;
	}
}

public class Test4 {
	public static void main(String args[]) {
		try {
			List list = new ArrayList();

			ProcessBuilder pb = null;
			Process p = null;
			// list the files and directorys under C:\
			list.add("CMD.EXE");
			list.add("/C");
			list.add("dir");
			pb = new ProcessBuilder(list);
			pb.directory(new File("C:\\"));
			p = pb.start();
			// process error and output message
			StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),
					"ERROR");
			StreamWatch outputWatch = new StreamWatch(p.getInputStream(),
					"OUTPUT");
			// start to watch
			errorWatch.start();
			outputWatch.start();
			// wait for exit
			int exitVal = p.waitFor();
			// print the content from ERROR and OUTPUT
			System.out.println("ERROR: " + errorWatch.getOutput());
			System.out.println("OUTPUT: " + outputWatch.getOutput());
			System.out.println("the return code is " + exitVal);
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
}



7、在Java中执行Java程序

  执行一个Java程序的关键在于:

  (1)知道JAVA虚拟机的位置,即java.exe或者java的路径

  (2)知道要执行的java程序的位置

  (3)知道该程序所依赖的其他类的位置

(1)待执行的Java类

 public class MyTest {

  public static void main(String[] args) {

  System.out.println("OUTPUT one");

  System.out.println("OUTPUT two");

  System.err.println("ERROR 1");

  System.err.println("ERROR 2");

  for(int i = 0; i < args.length; i++)

  {

  System.out.printf("args[%d] = %s.", i, args[i]);

  }

  }

  }


(2)执行该类的程序

 import java.util.*;

  import java.io.*;

  class StreamWatch extends Thread {

  InputStream is;

  String type;

  List output = new ArrayList();

  boolean debug = false;

  StreamWatch(InputStream is, String type) {

  this(is, type, false);

  }

  StreamWatch(InputStream is, String type, boolean debug) {

  this.is = is;

  this.type = type;

  this.debug = debug;

  }

  public void run() {

  try {

  PrintWriter pw = null;

  InputStreamReader isr = new InputStreamReader(is);

  BufferedReader br = new BufferedReader(isr);

  String line = null;

  while ((line = br.readLine()) != null) {

  output.add(line);

  if (debug)

  System.out.println(type + ">" + line);

  }

  if (pw != null)

  pw.flush();

  } catch (IOException ioe) {

  ioe.printStackTrace();

  }

  }

  public List getOutput() {

  return output;

  }

  }

  public class Test6 {

  public static void main(String args[]) {

  try {

  List list = new ArrayList();

  ProcessBuilder pb = null;

  Process p = null;

  String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";

  String classpath = System.getProperty("java.class.path");

  // list the files and directorys under C:\

  list.add(java);

  list.add("-classpath");

  list.add(classpath);

  list.add(MyTest.class.getName());

  list.add("hello");

  list.add("world");

  list.add("good better best");

  pb = new ProcessBuilder(list);

  p = pb.start();

  System.out.println(pb.command());

  // process error and output message

  StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),

  "ERROR");

  StreamWatch outputWatch = new StreamWatch(p.getInputStream(),

  "OUTPUT");

  // start to watch

  errorWatch.start();

  outputWatch.start();

  //wait for exit

  int exitVal = p.waitFor();

  //print the content from ERROR and OUTPUT

  System.out.println("ERROR: " + errorWatch.getOutput());

  System.out.println("OUTPUT: " + outputWatch.getOutput());

  System.out.println("the return code is " + exitVal);

  } catch (Throwable t) {

  t.printStackTrace();

  }

  }

  }


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics