创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。


Java调用其他程序时waitFor()阻塞
前段时间在工作中遇到这样一个问题,java代码中调用一个bat程序,在linux下执行完好,但是换到windows下就一直挂在那里不动了~

代码如下:

复制代码

public class CMDTest { 

 public static void main(String[] args) { 

 Process p = null; 

 try { 

 p = Runtime.getRuntime().exec("c:\\test.bat"); 


 p.waitFor(); 

 System.out.println(p.exitValue()); 

 System.out.println("over"); 

 } catch (Exception e) { 

 e.printStackTrace(); 

 } 

 } 

}


复制代码
其中,test.bat里就是一些命令操作,在这里就写一个简单的ping命令。

ping www.baidu.com
上面的代码运行之后,等了半天都没反应。Process的api中有如下说明:

复制代码
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。

创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。
创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。
因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
复制代码
也就是说:如果程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。

这就是问题所在!之后我查了下网上的解决办法,多数是创建两个线程在waitFor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。

按照这个思路,我写了如下util方法

复制代码

import java.io.BufferedReader; 

import java.io.IOException; 

import java.io.InputStream; 

import java.io.InputStreamReader; 

import java.util.ArrayList; 

import java.util.List; 


public class CommandUtil { 

 // 保存进程的输入流信息 

 private List<String> stdoutList = new ArrayList<String>(); 

 // 保存进程的错误流信息 

 private List<String> erroroutList = new ArrayList<String>(); 


 public void executeCommand(String command) { 

 // 先清空 

 stdoutList.clear(); 

 erroroutList.clear(); 


 Process p = null; 

 try { 

 p = Runtime.getRuntime().exec(command); 


 // 创建2个线程,分别读取输入流缓冲区和错误流缓冲区 

 ThreadUtil stdoutUtil = new ThreadUtil(p.getInputStream(), stdoutList); 

 ThreadUtil erroroutUtil = new ThreadUtil(p.getErrorStream(), erroroutList); 

 //启动线程读取缓冲区数据 

 stdoutUtil.start(); 

 erroroutUtil.start(); 


 p.waitFor(); 

 } catch (IOException e) { 

 e.printStackTrace(); 

 } catch (InterruptedException e) { 

 e.printStackTrace(); 

 } 

 } 


 public List<String> getStdoutList() { 

 return stdoutList; 

 } 


 public List<String> getErroroutList() { 

 return erroroutList; 

 } 


} 


class ThreadUtil implements Runnable { 

 // 设置读取的字符编码 

 private String character = "GB2312"; 

 private List<String> list; 

 private InputStream inputStream; 


 public ThreadUtil(InputStream inputStream, List<String> list) { 

 this.inputStream = inputStream; 

 this.list = list; 

 } 


 public void start() { 

 Thread thread = new Thread(this); 

 thread.setDaemon(true);//将其设置为守护线程 

 thread.start(); 

 } 


 public void run() { 

 BufferedReader br = null; 

 try { 

 br = new BufferedReader(new InputStreamReader(inputStream, character)); 

 String line = null; 

 while ((line = br.readLine()) != null) { 

 if (line != null) { 

 list.add(line); 

 } 

 } 

 } catch (IOException e) { 

 e.printStackTrace(); 

 } finally { 

 try { 

 //释放资源 

 inputStream.close(); 

 br.close(); 

 } catch (IOException e) { 

 e.printStackTrace(); 

 } 

 } 

 } 


}


复制代码
再整个方法测试下:

复制代码

import java.util.List; 


public class TestMain { 

 public static void main(String[] args) { 

 CommandUtil util = new CommandUtil(); 

 util.executeCommand("c:\\test.bat"); 

 printList(util.getStdoutList()); 

 System.out.println("--------------------"); 

 printList(util.getErroroutList()); 

 } 



 public static void printList(List<String> list){ 

 for (String string : list) { 

 System.out.println(string); 

 } 

 } 


}


复制代码
这样一来,问题确实解决了,再也不会出现阻塞了~