• 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;
    
  • 相关阅读:
    Flask之flask-script
    Flask之flask-sqlalchemy
    sqlalchemy之基础操作
    Linux之服务管理
    python之PEP8规范
    app开发-1
    Mongodb之pymongo
    Mongodb之增删改查操作
    Mongodb之数据类型
    linux的virtualenv和virtualenvwarpper
  • 原文地址:https://www.cnblogs.com/xing901022/p/5557973.html
Copyright © 2020-2023  润新知