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(); // 創建子進程
相關文章
相關標籤/搜索