Java 多进程

1.Java进程的建立 
Java提供了两种方法用来启动进程或其它程序: 
(1)使用Runtime的exec()方法 
(2)使用ProcessBuilder的start()方法 
1.1 ProcessBuilder 
   ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于建立操做系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在J2SE 1.5以前,都是由Process类处来实现进程的控制管理。 
每一个 ProcessBuilder 实例管理一个进程属性集。start() 方法利用这些属性建立一个新的 Process 实例。start() 方法能够从同一实例重复调用,以利用相同的或相关的属性建立新的子进程。 
每一个进程生成器管理这些进程属性: 
  命令 是一个字符串列表,它表示要调用的外部程序文件及其参数(若是有)。在此,表示有效的操做系统命令的字符串列表是依赖于系统的。例如,每个整体变量,一般都要成为此列表中的元素,但有一些操做系统,但愿程序能本身标记命令行字符串——在这种系统中,Java 实现可能须要命令确切地包含这两个元素。 
  环境 是从变量 到值 的依赖于系统的映射。初始值是当前进程环境的一个副本(请参阅 System.getenv())。 
工做目录。默认值是当前进程的当前工做目录,一般根据系统属性 user.dir 来命名。 
  redirectErrorStream 属性。最初,此属性为 false,意思是子进程的标准输出和错误输出被发送给两个独立的流,这些流能够经过 Process.getInputStream() 和 Process.getErrorStream() 方法来访问。若是将值设置为 true,标准错误将与标准输出合并。这使得关联错误消息和相应的输出变得更容易。在此状况下,合并的数据可从 Process.getInputStream() 返回的流读取,而从 Process.getErrorStream() 返回的流读取将直接到达文件尾。 
修改进程构建器的属性将影响后续由该对象的 start() 方法启动的进程,但从不会影响之前启动的进程或 Java 自身的进程。大多数错误检查由 start() 方法执行。能够修改对象的状态,但这样 start() 将会失败。例如,将命令属性设置为一个空列表将不会抛出异常,除非包含了 start()。 
注意,此类不是同步的。若是多个线程同时访问一个 ProcessBuilder,而其中至少一个线程从结构上修改了其中一个属性,它必须 保持外部同步。 
构造方法摘要  
ProcessBuilder(List<String> command)   
          利用指定的操做系统程序和参数构造一个进程生成器。    
ProcessBuilder(String... command)   
          利用指定的操做系统程序和参数构造一个进程生成器。    
  
方法摘要  
 List<String> command()   
          返回此进程生成器的操做系统程序和参数。  
 ProcessBuilder command(List<String> command)   
          设置此进程生成器的操做系统程序和参数。  
 ProcessBuilder command(String... command)   
          设置此进程生成器的操做系统程序和参数。  
 File directory()   
          返回此进程生成器的工做目录。  
 ProcessBuilder directory(File directory)   
          设置此进程生成器的工做目录。  
 Map<String,String> environment()   
          返回此进程生成器环境的字符串映射视图。  
 boolean redirectErrorStream()   
          通知进程生成器是否合并标准错误和标准输出。  
 ProcessBuilder redirectErrorStream(boolean redirectErrorStream)   
          设置此进程生成器的 redirectErrorStream 属性。  
 Process start()   
          使用此进程生成器的属性启动一个新进程。 java

1.2 Runtime 
  每一个 Java 应用程序都有一个 Runtime 类实例,使应用程序可以与其运行的环境相链接。能够经过 getRuntime 方法获取当前运行时。 
  应用程序不能建立本身的 Runtime 类实例。但能够经过 getRuntime 方法获取当前Runtime运行时对象的引用。一旦获得了一个当前的Runtime对象的引用,就能够调用Runtime对象的方法去控制Java虚拟机的状态和行为。 
Java代码  收藏代码
void addShutdownHook(Thread hook)   
          注册新的虚拟机来关闭挂钩。  
 int availableProcessors()   
          向 Java 虚拟机返回可用处理器的数目。  
 Process exec(String command)   
          在单独的进程中执行指定的字符串命令。  
 Process exec(String[] cmdarray)   
          在单独的进程中执行指定命令和变量。  
 Process exec(String[] cmdarray, String[] envp)   
          在指定环境的独立进程中执行指定命令和变量。  
 Process exec(String[] cmdarray, String[] envp, File dir)   
          在指定环境和工做目录的独立进程中执行指定的命令和变量。  
 Process exec(String command, String[] envp)   
          在指定环境的单独进程中执行指定的字符串命令。  
 Process exec(String command, String[] envp, File dir)   
          在有指定环境和工做目录的独立进程中执行指定的字符串命令。  
 void exit(int status)   
          经过启动虚拟机的关闭序列,终止当前正在运行的 Java 虚拟机。  
 long freeMemory()   
          返回 Java 虚拟机中的空闲内存量。  
 void gc()   
          运行垃圾回收器。  
 InputStream getLocalizedInputStream(InputStream in)   
          已过期。 从 JDK 1.1 开始,将本地编码字节流转换为 Unicode 字符流的首选方法是使用 InputStreamReader 和 BufferedReader 类。  
 OutputStream getLocalizedOutputStream(OutputStream out)   
          已过期。 从 JDK 1.1 开始,将 Unicode 字符流转换为本地编码字节流的首选方法是使用 OutputStreamWriter、BufferedWriter 和 PrintWriter 类。  
static Runtime getRuntime()   
          返回与当前 Java 应用程序相关的运行时对象。  
 void halt(int status)   
          强行终止目前正在运行的 Java 虚拟机。  
 void load(String filename)   
          加载做为动态库的指定文件名。  
 void loadLibrary(String libname)   
          加载具备指定库名的动态库。  
 long maxMemory()   
          返回 Java 虚拟机试图使用的最大内存量。  
 boolean removeShutdownHook(Thread hook)   
          取消注册某个先前已注册的虚拟机关闭挂钩。  
 void runFinalization()   
          运行挂起 finalization 的全部对象的终止方法。  
static void runFinalizersOnExit(boolean value)   
          已过期。 此方法自己具备不安全性。它可能对正在使用的对象调用终结方法,而其余线程正在操做这些对象,从而致使不正确的行为或死锁。  
 long totalMemory()   
          返回 Java 虚拟机中的内存总量。  
 void traceInstructions(boolean on)   
          启用/禁用指令跟踪。  
 void traceMethodCalls(boolean on)   
          启用/禁用方法调用跟踪。 编程

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

void destroy()   
          杀掉子进程。  
         通常状况下,该方法并不能杀掉已经启动的进程,不用为好。  
int exitValue()   
          返回子进程的出口值。   
          只有启动的进程执行完成、或者因为异常退出后,exitValue()方法才会有正常的返回值,不然抛出异常。  
InputStream getErrorStream()   
          获取子进程的错误流。  
         若是错误输出被重定向,则不能从该流中读取错误输出。  
InputStream getInputStream()   
          获取子进程的输入流。  
          能够从该流中读取进程的标准输出。  
OutputStream getOutputStream()   
          获取子进程的输出流。  
          写入到该流中的数据做为进程的标准输入。  
int waitFor()   
          致使当前线程等待,若有必要,一直要等到由该 Process 对象表示的进程已经终止。安全

2.多进程编程实例
通常咱们在java中运行其它类中的方法时,不管是静态调用,仍是动态调用,都是在当前的进程中执行的,也就是说,只有一个java虚拟机实例在运行。而有的时候,咱们须要经过java代码启动多个java子进程。这样作虽然占用了一些系统资源,但会使程序更加稳定,由于新启动的程序是在不一样的虚拟机进程中运行的,若是有一个进程发生异常,并不影响其它的子进程。异步

  在Java中咱们可使用两种方法来实现这种要求。最简单的方法就是经过Runtime中的exec方法执行java classname。若是执行成功,这个方法返回一个Process对象,若是执行失败,将抛出一个IOException错误。下面让咱们来看一个简单的例子。ui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Test1.java文件
import  java.io.*;
public  class  Test
{
  public  static  void  main(String[] args)
 {
  FileOutputStream fOut = new  FileOutputStream( "c:\\Test1.txt" );
  fOut.close();
  System.out.println( "被调用成功!" );
 }
}
 
// Test_Exec.java
public  class  Test_Exec
{
  public  static  void  main(String[] args)
 {
  Runtime run = Runtime.getRuntime();
  Process p = run.exec( "java test1" );
 }
}

  经过java Test_Exec运行程序后,发如今C盘多了个Test1.txt文件,但在控制台中并未出现"被调用成功!"的输出信息。所以能够判定,Test已经被执行成功,但由于某种缘由,Test的输出信息未在Test_Exec的控制台中输出。这个缘由也很简单,由于使用exec创建的是Test_Exec的子进程,这个子进程并无本身的控制台,所以,它并不会输出任何信息。编码

  若是要输出子进程的输出信息,能够经过Process中的getInputStream获得子进程的输出流(在子进程中输出,在父进程中就是输入),而后将子进程中的输出流从父进程的控制台输出。具体的实现代码以下如示:spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Test_Exec_Out.java
import  java.io.*;
public  class  Test_Exec_Out
{
  public  static  void  main(String[] args)
 {
  Runtime run = Runtime.getRuntime();
  Process p = run.exec( "java test1" );
  BufferedInputStream in = new  BufferedInputStream(p.getInputStream());
  BufferedReader br = new  BufferedReader( new  InputStreamReader(in));
  String s;
   while  ((s = br.readLine()) != null )
   System.out.println(s);
 }
}

  从上面的代码能够看出,在Test_Exec_Out.java中经过按行读取子进程的输出信息,而后在Test_Exec_Out中按每行进行输出。 上面讨论的是如何获得子进程的输出信息。那么,除了输出信息,还有输入信息。既然子进程没有本身的控制台,那么输入信息也得由父进程提供。咱们能够经过Process的getOutputStream方法来为子进程提供输入信息(即由父进程向子进程输入信息,而不是由控制台输入信息)。咱们能够看看以下的代码:操作系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Test2.java文件
import  java.io.*;
public  class  Test
{
  public  static  void  main(String[] args)
 {
  BufferedReader br = new  BufferedReader( new  InputStreamReader(System.in));
  System.out.println( "由父进程输入的信息:"  + br.readLine());
 }
}
 
// Test_Exec_In.java
import  java.io.*;
public  class  Test_Exec_In
{
  public  static  void  main(String[] args)
 {
  Runtime run = Runtime.getRuntime();
  Process p = run.exec( "java test2" );
  BufferedWriter bw = new  BufferedWriter( new  OutputStreamWriter(p.getOutputStream()));
  bw.write( "向子进程输出信息" );
  bw.flush();
  bw.close(); // 必须得关闭流,不然没法向子进程中输入信息
   // System.in.read();
 }
}

  从以上代码能够看出,Test1获得由Test_Exec_In发过来的信息,并将其输出。当你不加bw.flash()和bw.close()时,信息将没法到达子进程,也就是说子进程进入阻塞状态,但因为父进程已经退出了,所以,子进程也跟着退出了。若是要证实这一点,能够在最后加上System.in.read(),而后经过任务管理器(在windows下)查看java进程,你会发现若是加上bw.flush()和bw.close(),只有一个java进程存在,若是去掉它们,就有两个java进程存在。这是由于,若是将信息传给Test2,在获得信息后,Test2就退出了。在这里有一点须要说明一下,exec的执行是异步的,并不会由于执行的某个程序阻塞而中止执行下面的代码。所以,能够在运行test2后,仍能够执行下面的代码。
exec方法通过了屡次的重载。上面使用的只是它的一种重载。它还能够将命令和参数分开,如exec("java.test2")能够写成exec("java", "test2")。exec还能够经过指定的环境变量运行不一样配置的java虚拟机。命令行

  除了使用Runtime的exec方法创建子进程外,还能够经过ProcessBuilder创建子进程。ProcessBuilder的使用方法以下:

1
2
3
4
5
6
7
8
9
10
11
// Test_Exec_Out.java
import  java.io.*;
public  class  Test_Exec_Out
{
  public  static  void  main(String[] args)
 {
  ProcessBuilder pb = new  ProcessBuilder( "java" , "test1" );
  Process p = pb.start();
  … …
 }
}

  在创建子进程上,ProcessBuilder和Runtime相似,不一样的ProcessBuilder使用start()方法启动子进程,而Runtime使用exec方法启动子进程。获得Process后,它们的操做就彻底同样的。

  ProcessBuilder和Runtime同样,也可设置可执行文件的环境信息、工做目录等。下面的例子描述了如何使用ProcessBuilder设置这些信息。

1
2
3
4
5
6
7
8
ProcessBuilder pb = new  ProcessBuilder( "Command" , "arg2" , "arg2" , '' ');
// 设置环境变量
Map<String, String> env = pb.environment();
env.put( "key1" , "value1" );
env.remove( "key2" );
env.put( "key2" , env.get( "key1" ) + "_test" );
pb.directory( "..\abcd" ); // 设置工做目录
Process p = pb.start(); // 创建子进程
相关文章
相关标签/搜索