解决waitfor()阻塞问题
运行代码执行exe,shell这样的程序或脚本再java中需:
(1) 使用Runtime的exec()方法
(2) 使用ProcessBuilder的start()方法
Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。
但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。
Process的api中有如下说明:
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。 创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。 创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。 因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。
也就是说:如果程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。
多数是创建两个线程在waitFor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。
package DailyTest; 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> stdotList = new ArrayList<>(); //保存进程标准错误流信息 private List<String> errorList = new ArrayList<>(); public void executeCommand(String command){ stdotList.clear(); errorList.clear(); Process p =null; try { p = Runtime.getRuntime().exec(command); //创建两个线程 分别读取输入流缓冲区和错误流缓冲区 ThreadUtil stdotThread = new ThreadUtil(stdotList,p.getInputStream()); ThreadUtil errorThread = new ThreadUtil(errorList,p.getErrorStream()); stdotThread.start(); errorThread.start(); p.waitFor(); //一直挂起,直到子进程执行结束 //返回值0表示正常退出 } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public List<String> getErrorList() { return errorList; } public List<String> getStdotList() { return stdotList; } } class ThreadUtil implements Runnable{ //属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。 private String character1 = "ISO-8859-1"; //汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。 // 其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。 private String character2 = "GB2312"; //最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。 private String character3 = "unicode"; //相比于unicode会使用更少的空间,但如果已经知道是汉字 推荐GB2312 private String character4 = "utf-8"; private List<String> list; private InputStream inputStream; public ThreadUtil(List<String> list,InputStream inputStream){ this.list = list; this.inputStream = inputStream; } public void start(){ Thread thread = new Thread(this); thread.setDaemon(true); //设置为守护线程 //定义:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。 thread.start(); } @Override public void run() { BufferedReader br =null; try { br = new BufferedReader(new InputStreamReader(inputStream,character2)); String line =null; while (null != (line = br.readLine())){ list.add(line); } }catch (IOException e){ e.printStackTrace(); }finally { try { inputStream.close(); br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
package DailyTest; import java.util.List; public class TestCommandUtil { //快速修改类名 shift+F6 public static void main(String args []){ CommandUtil commandUtil =new CommandUtil(); commandUtil.executeCommand("javac"); printList(commandUtil.getStdotList()); System.out.println("--------------------"); printList(commandUtil.getErrorList()); } public static void printList(List<String> list){ for (String string : list) { System.out.println(string); } } }
这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。
问题的关键是处在输入流缓冲区那个地方,子进程的产生的输出流没有被JVM及时的读取最后缓冲区满了就卡住了。
至于能够不让子进程向输入流写入数据,是不是可以解决这个问题,还有待考证。