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;
原文地址:https://www.cnblogs.com/xing901022/p/5557973.html