1.Java進(jìn)程的創(chuàng)建
Java提供了兩種方法用來(lái)啟動(dòng)進(jìn)程或其它程序:
(1)使用Runtime的exec()方法
(2)使用ProcessBuilder的start()方法
1.1 ProcessBuilder
ProcessBuilder類是J2SE 1.5在java.lang中新添加的一個(gè)新類,此類用于創(chuàng)建操作系統(tǒng)進(jìn)程,它提供一種啟動(dòng)和管理進(jìn)程(也就是應(yīng)用程序)的方法。在J2SE 1.5之前,都是由Process類處來(lái)實(shí)現(xiàn)進(jìn)程的控制管理。
每個(gè) ProcessBuilder 實(shí)例管理一個(gè)進(jìn)程屬性集。start() 方法利用這些屬性創(chuàng)建一個(gè)新的 Process 實(shí)例。start() 方法可以從同一實(shí)例重復(fù)調(diào)用,以利用相同的或相關(guān)的屬性創(chuàng)建新的子進(jìn)程。
每個(gè)進(jìn)程生成器管理這些進(jìn)程屬性:
命令 是一個(gè)字符串列表,它表示要調(diào)用的外部程序文件及其參數(shù)(如果有)。在此,表示有效的操作系統(tǒng)命令的字符串列表是依賴于系統(tǒng)的。例如,每一個(gè)總體變量,通常都要成為此列表中的元素,但有一些操作系統(tǒng),希望程序能自己標(biāo)記命令行字符串——在這種系統(tǒng)中,Java 實(shí)現(xiàn)可能需要命令確切地包含這兩個(gè)元素。
環(huán)境 是從變量 到值 的依賴于系統(tǒng)的映射。初始值是當(dāng)前進(jìn)程環(huán)境的一個(gè)副本(請(qǐng)參閱 System.getenv())。
工作目錄。默認(rèn)值是當(dāng)前進(jìn)程的當(dāng)前工作目錄,通常根據(jù)系統(tǒng)屬性 user.dir 來(lái)命名。
redirectErrorStream 屬性。最初,此屬性為 false,意思是子進(jìn)程的標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出被發(fā)送給兩個(gè)獨(dú)立的流,這些流可以通過(guò) Process.getInputStream() 和 Process.getErrorStream() 方法來(lái)訪問(wèn)。如果將值設(shè)置為 true,標(biāo)準(zhǔn)錯(cuò)誤將與標(biāo)準(zhǔn)輸出合并。這使得關(guān)聯(lián)錯(cuò)誤消息和相應(yīng)的輸出變得更容易。在此情況下,合并的數(shù)據(jù)可從 Process.getInputStream() 返回的流讀取,而從 Process.getErrorStream() 返回的流讀取將直接到達(dá)文件尾。
修改進(jìn)程構(gòu)建器的屬性將影響后續(xù)由該對(duì)象的 start() 方法啟動(dòng)的進(jìn)程,但從不會(huì)影響以前啟動(dòng)的進(jìn)程或 Java 自身的進(jìn)程。大多數(shù)錯(cuò)誤檢查由 start() 方法執(zhí)行。可以修改對(duì)象的狀態(tài),但這樣 start() 將會(huì)失敗。例如,將命令屬性設(shè)置為一個(gè)空列表將不會(huì)拋出異常,除非包含了 start()。
注意,此類不是同步的。如果多個(gè)線程同時(shí)訪問(wèn)一個(gè) ProcessBuilder,而其中至少一個(gè)線程從結(jié)構(gòu)上修改了其中一個(gè)屬性,它必須 保持外部同步。
構(gòu)造方法摘要
ProcessBuilder(List<String> command)
利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個(gè)進(jìn)程生成器。
ProcessBuilder(String... command)
利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個(gè)進(jìn)程生成器。
方法摘要
List<String> command()
返回此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
ProcessBuilder command(List<String> command)
設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
ProcessBuilder command(String... command)
設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
File directory()
返回此進(jìn)程生成器的工作目錄。
ProcessBuilder directory(File directory)
設(shè)置此進(jìn)程生成器的工作目錄。
Map<String,String> environment()
返回此進(jìn)程生成器環(huán)境的字符串映射視圖。
boolean redirectErrorStream()
通知進(jìn)程生成器是否合并標(biāo)準(zhǔn)錯(cuò)誤和標(biāo)準(zhǔn)輸出。
ProcessBuilder redirectErrorStream(boolean redirectErrorStream)
設(shè)置此進(jìn)程生成器的 redirectErrorStream 屬性。
Process start()
使用此進(jìn)程生成器的屬性啟動(dòng)一個(gè)新進(jìn)程。
1.2 Runtime
每個(gè) Java 應(yīng)用程序都有一個(gè) Runtime 類實(shí)例,使應(yīng)用程序能夠與其運(yùn)行的環(huán)境相連接。可以通過(guò) getRuntime 方法獲取當(dāng)前運(yùn)行時(shí)。
應(yīng)用程序不能創(chuàng)建自己的 Runtime 類實(shí)例。但可以通過(guò) getRuntime 方法獲取當(dāng)前Runtime運(yùn)行時(shí)對(duì)象的引用。一旦得到了一個(gè)當(dāng)前的Runtime對(duì)象的引用,就可以調(diào)用Runtime對(duì)象的方法去控制Java虛擬機(jī)的狀態(tài)和行為。
Java代碼 收藏代碼
void addShutdownHook(Thread hook)
注冊(cè)新的虛擬機(jī)來(lái)關(guān)閉掛鉤。
int availableProcessors()
向 Java 虛擬機(jī)返回可用處理器的數(shù)目。
Process exec(String command)
在單獨(dú)的進(jìn)程中執(zhí)行指定的字符串命令。
Process exec(String[] cmdarray)
在單獨(dú)的進(jìn)程中執(zhí)行指定命令和變量。
Process exec(String[] cmdarray, String[] envp)
在指定環(huán)境的獨(dú)立進(jìn)程中執(zhí)行指定命令和變量。
Process exec(String[] cmdarray, String[] envp, File dir)
在指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的命令和變量。
Process exec(String command, String[] envp)
在指定環(huán)境的單獨(dú)進(jìn)程中執(zhí)行指定的字符串命令。
Process exec(String command, String[] envp, File dir)
在有指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的字符串命令。
void exit(int status)
通過(guò)啟動(dòng)虛擬機(jī)的關(guān)閉序列,終止當(dāng)前正在運(yùn)行的 Java 虛擬機(jī)。
long freeMemory()
返回 Java 虛擬機(jī)中的空閑內(nèi)存量。
void gc()
運(yùn)行垃圾回收器。
InputStream getLocalizedInputStream(InputStream in)
已過(guò)時(shí)。 從 JDK 1.1 開(kāi)始,將本地編碼字節(jié)流轉(zhuǎn)換為 Unicode 字符流的首選方法是使用 InputStreamReader 和 BufferedReader 類。
OutputStream getLocalizedOutputStream(OutputStream out)
已過(guò)時(shí)。 從 JDK 1.1 開(kāi)始,將 Unicode 字符流轉(zhuǎn)換為本地編碼字節(jié)流的首選方法是使用 OutputStreamWriter、BufferedWriter 和 PrintWriter 類。
static Runtime getRuntime()
返回與當(dāng)前 Java 應(yīng)用程序相關(guān)的運(yùn)行時(shí)對(duì)象。
void halt(int status)
強(qiáng)行終止目前正在運(yùn)行的 Java 虛擬機(jī)。
void load(String filename)
加載作為動(dòng)態(tài)庫(kù)的指定文件名。
void loadLibrary(String libname)
加載具有指定庫(kù)名的動(dòng)態(tài)庫(kù)。
long maxMemory()
返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量。
boolean removeShutdownHook(Thread hook)
取消注冊(cè)某個(gè)先前已注冊(cè)的虛擬機(jī)關(guān)閉掛鉤。
void runFinalization()
運(yùn)行掛起 finalization 的所有對(duì)象的終止方法。
static void runFinalizersOnExit(boolean value)
已過(guò)時(shí)。 此方法本身具有不安全性。它可能對(duì)正在使用的對(duì)象調(diào)用終結(jié)方法,而其他線程正在操作這些對(duì)象,從而導(dǎo)致不正確的行為或死鎖。
long totalMemory()
返回 Java 虛擬機(jī)中的內(nèi)存總量。
void traceInstructions(boolean on)
啟用/禁用指令跟蹤。
void traceMethodCalls(boolean on)
啟用/禁用方法調(diào)用跟蹤。
1.3 Process
不管通過(guò)那種方法啟動(dòng)進(jìn)程后,都會(huì)返回一個(gè)Process類的實(shí)例代表啟動(dòng)的進(jìn)程,該實(shí)例可用來(lái)控制進(jìn)程并獲得相關(guān)信息。Process 類提供了執(zhí)行從進(jìn)程輸入、執(zhí)行輸出到進(jìn)程、等待進(jìn)程完成、檢查進(jìn)程的退出狀態(tài)以及銷毀(殺掉)進(jìn)程的方法:
void destroy()
殺掉子進(jìn)程。
一般情況下,該方法并不能殺掉已經(jīng)啟動(dòng)的進(jìn)程,不用為好。
int exitValue()
返回子進(jìn)程的出口值。
只有啟動(dòng)的進(jìn)程執(zhí)行完成、或者由于異常退出后,exitValue()方法才會(huì)有正常的返回值,否則拋出異常。
InputStream getErrorStream()
獲取子進(jìn)程的錯(cuò)誤流。
如果錯(cuò)誤輸出被重定向,則不能從該流中讀取錯(cuò)誤輸出。
InputStream getInputStream()
獲取子進(jìn)程的輸入流。
可以從該流中讀取進(jìn)程的標(biāo)準(zhǔn)輸出。
OutputStream getOutputStream()
獲取子進(jìn)程的輸出流。
寫入到該流中的數(shù)據(jù)作為進(jìn)程的標(biāo)準(zhǔn)輸入。
int waitFor()
導(dǎo)致當(dāng)前線程等待,如有必要,一直要等到由該 Process 對(duì)象表示的進(jìn)程已經(jīng)終止。
2.多進(jìn)程編程實(shí)例
一般我們?cè)趈ava中運(yùn)行其它類中的方法時(shí),無(wú)論是靜態(tài)調(diào)用,還是動(dòng)態(tài)調(diào)用,都是在當(dāng)前的進(jìn)程中執(zhí)行的,也就是說(shuō),只有一個(gè)java虛擬機(jī)實(shí)例在運(yùn)行。而有的時(shí)候,我們需要通過(guò)java代碼啟動(dòng)多個(gè)java子進(jìn)程。這樣做雖然占用了一些系統(tǒng)資源,但會(huì)使程序更加穩(wěn)定,因?yàn)樾聠?dòng)的程序是在不同的虛擬機(jī)進(jìn)程中運(yùn)行的,如果有一個(gè)進(jìn)程發(fā)生異常,并不影響其它的子進(jìn)程。
在Java中我們可以使用兩種方法來(lái)實(shí)現(xiàn)這種要求。最簡(jiǎn)單的方法就是通過(guò)Runtime中的exec方法執(zhí)行java classname。如果執(zhí)行成功,這個(gè)方法返回一個(gè)Process對(duì)象,如果執(zhí)行失敗,將拋出一個(gè)IOException錯(cuò)誤。下面讓我們來(lái)看一個(gè)簡(jiǎn)單的例子。
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( "被調(diào)用成功!" ); } } // Test_Exec.java public class Test_Exec { public static void main(String[] args) { Runtime run = Runtime.getRuntime(); Process p = run.exec( "java test1" ); } } |
通過(guò)java Test_Exec運(yùn)行程序后,發(fā)現(xiàn)在C盤多了個(gè)Test1.txt文件,但在控制臺(tái)中并未出現(xiàn)"被調(diào)用成功!"的輸出信息。因此可以斷定,Test已經(jīng)被執(zhí)行成功,但因?yàn)槟撤N原因,Test的輸出信息未在Test_Exec的控制臺(tái)中輸出。這個(gè)原因也很簡(jiǎn)單,因?yàn)槭褂胑xec建立的是Test_Exec的子進(jìn)程,這個(gè)子進(jìn)程并沒(méi)有自己的控制臺(tái),因此,它并不會(huì)輸出任何信息。
如果要輸出子進(jìn)程的輸出信息,可以通過(guò)Process中的getInputStream得到子進(jìn)程的輸出流(在子進(jìn)程中輸出,在父進(jìn)程中就是輸入),然后將子進(jìn)程中的輸出流從父進(jìn)程的控制臺(tái)輸出。具體的實(shí)現(xiàn)代碼如下如示:
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中通過(guò)按行讀取子進(jìn)程的輸出信息,然后在Test_Exec_Out中按每行進(jìn)行輸出。 上面討論的是如何得到子進(jìn)程的輸出信息。那么,除了輸出信息,還有輸入信息。既然子進(jìn)程沒(méi)有自己的控制臺(tái),那么輸入信息也得由父進(jìn)程提供。我們可以通過(guò)Process的getOutputStream方法來(lái)為子進(jìn)程提供輸入信息(即由父進(jìn)程向子進(jìn)程輸入信息,而不是由控制臺(tái)輸入信息)。我們可以看看如下的代碼:
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( "由父進(jìn)程輸入的信息:" + 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( "向子進(jìn)程輸出信息" ); bw.flush(); bw.close(); // 必須得關(guān)閉流,否則無(wú)法向子進(jìn)程中輸入信息 // System.in.read(); } } |
從以上代碼可以看出,Test1得到由Test_Exec_In發(fā)過(guò)來(lái)的信息,并將其輸出。當(dāng)你不加bw.flash()和bw.close()時(shí),信息將無(wú)法到達(dá)子進(jìn)程,也就是說(shuō)子進(jìn)程進(jìn)入阻塞狀態(tài),但由于父進(jìn)程已經(jīng)退出了,因此,子進(jìn)程也跟著退出了。如果要證明這一點(diǎn),可以在最后加上System.in.read(),然后通過(guò)任務(wù)管理器(在windows下)查看java進(jìn)程,你會(huì)發(fā)現(xiàn)如果加上bw.flush()和bw.close(),只有一個(gè)java進(jìn)程存在,如果去掉它們,就有兩個(gè)java進(jìn)程存在。這是因?yàn)椋绻麑⑿畔鹘oTest2,在得到信息后,Test2就退出了。在這里有一點(diǎn)需要說(shuō)明一下,exec的執(zhí)行是異步的,并不會(huì)因?yàn)閳?zhí)行的某個(gè)程序阻塞而停止執(zhí)行下面的代碼。因此,可以在運(yùn)行test2后,仍可以執(zhí)行下面的代碼。
exec方法經(jīng)過(guò)了多次的重載。上面使用的只是它的一種重載。它還可以將命令和參數(shù)分開(kāi),如exec("java.test2")可以寫成exec("java", "test2")。exec還可以通過(guò)指定的環(huán)境變量運(yùn)行不同配置的java虛擬機(jī)。
除了使用Runtime的exec方法建立子進(jìn)程外,還可以通過(guò)ProcessBuilder建立子進(jìn)程。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(); … … } } |
在建立子進(jìn)程上,ProcessBuilder和Runtime類似,不同的ProcessBuilder使用start()方法啟動(dòng)子進(jìn)程,而Runtime使用exec方法啟動(dòng)子進(jìn)程。得到Process后,它們的操作就完全一樣的。
ProcessBuilder和Runtime一樣,也可設(shè)置可執(zhí)行文件的環(huán)境信息、工作目錄等。下面的例子描述了如何使用ProcessBuilder設(shè)置這些信息。
1
2
3
4
5
6
7
8
|
ProcessBuilder pb = new ProcessBuilder( "Command" , "arg2" , "arg2" , '' '); // 設(shè)置環(huán)境變量 Map<String, String> env = pb.environment(); env.put( "key1" , "value1" ); env.remove( "key2" ); env.put( "key2" , env.get( "key1" ) + "_test" ); pb.directory( "..\abcd" ); // 設(shè)置工作目錄 Process p = pb.start(); // 建立子進(jìn)程 |