Java程序員的日常 —— 多進程開發IO阻塞問題


本篇仍舊是源於最近的工作,總結一下紀念那些年埋下的坑...

背景故事

  • 需求:“使用進程方式啟動另一個程序!”

  • 開發:“OK! Runtime.getRuntime().exec("xxxx")”

  • 需求:“啟動以后能看到輸出消息不!”

  • 開發:“OK!”

Process process = null;
try {
	process = Runtime.getRuntime().exec("ipconfig /all");
} catch (IOException e) {
	e.printStackTrace();
}
try {
	String line;
	InputStream is = process.getInputStream();
	BufferedReader br = new BufferedReader(new InputStreamReader(is,"GBK"));
		
	while(null != (line = br.readLine())){
		System.out.println(line);
	}
	System.out.println("---------------------------------------------------------------------------------------");
	InputStream is_error = process.getErrorStream();
	BufferedReader br_error = new BufferedReader(new InputStreamReader(is_error,"GBK"));
		
	while(null != (line = br_error.readLine())){
		System.out.println(line);
	}
} catch (IOException e) {
	e.printStackTrace();
}

於是,神坑挖好了!

遇到的問題

由於運行的程序比較復雜,有可能出現錯誤輸出。這時就不好保證是錯誤輸出還是標准輸出哪個先到。但是上面的程序中,使用了同步的方式輸出子進程的消息,結果就導致了子進程阻塞。

解決方案1:使用緩沖區緩存消息

這個可以參考CSDN的帖子

解決方案2:使用ProcessBuilder合並標准輸出和錯誤

仍然源自於上面的博客:

try{
	String[] cmds = {"ipconfig","/all"};
	ProcessBuilder builder = new ProcessBuilder(cmds);    
	
	//合並輸出流和錯誤流
	builder.redirectErrorStream(true);    
			 
	//啟動進程
	Process process = builder.start();    
			 
	//獲得輸出流
	BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));    
	String line = null;    
	while (null != (line = br.readLine())){    
		System.out.println(line);
	}    
}catch(IOException e){
	e.printStackTrace();
}

上面的代碼中builder.redirectErrorStream(true);就可以幫助你把錯誤合並到標准輸出里面。

於是,很好奇這個ProcessBuilder到底什么東東。

閱讀API —— 什么是ProcessBuilder

ProcessBuilder用於創建操作系統進程,每個ProcessBuilder實例都管理一個進程屬性集合。通過調用start()方法,可以通過這些屬性創建出一個進程。start()方法可以被多次調用,來創建多個獨立的進程。

每個builder管理着下面的進程屬性:

cmmand

命令,比如{“ipcofig”,"/all"}

environment

環境變量,子進程會直接使用當前進程的環境變量。環境變量是獨立的,因此可以被修改,但是不會影響其他的進程。

directory

工作目錄,如果返回的是Null,說明當前目錄使用的是系統變量user.dir所在的目錄。

redirectErrorStream屬性

默認是false。Flase意味着標准輸出和標准錯誤是兩個獨立的流,可以通過Process.getInputStream()和Process.getErrorStream()方法獲得。

如果這個值設置為true,那么標准錯誤將會合並到標准輸出中,並且發往同一個目標地址(這種特性使得錯誤消息可以很方便的和輸出消息一起管理),此時,如果你再想要單獨獲取錯誤輸出流,就會得到null。

線程安全

注意這個類不是線程安全的,因此如果多個線程使用ProcessBuilder實例,並且修改屬性,那么可能會造成沖突。因此需要在外面進行線程同步。

啟動

可以簡單的向下面這樣啟動一個進程:

 Process p = new ProcessBuilder("myCommand", "myArg").start();

樣例

下面是官方文檔中給出的樣例,樣例中修改了工作目錄以及環境變量,並且把標准錯誤和標准輸出合並輸出到日志文件中:

 ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
 Map<String, String> env = pb.environment();
 env.put("VAR1", "myValue");
 env.remove("OTHERVAR");
 env.put("VAR2", env.get("VAR1") + "suffix");
 pb.directory(new File("myDir"));
 File log = new File("log");
 pb.redirectErrorStream(true);
 pb.redirectOutput(Redirect.appendTo(log));
 Process p = pb.start();
 assert pb.redirectInput() == Redirect.PIPE;
 assert pb.redirectOutput().file() == log;
 assert p.getInputStream().read() == -1;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM