Java Runtime的执行与流读取阻塞的问题分析

参考资料:

实现案例:http://blog.csdn.net/lu8000/article/details/27518663

阻塞处理:http://www.jroller.com/ethdsy/entry/process_blocking_on_their_output

进程挂起:http://www.zhixing123.cn/jsp/44146.html

Java Runtime分析: 

英文 http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html

中文 http://jiangshuiy.iteye.com/blog/1674235

 

Java中 Runtime.getInstance().exec (String cmd)

或者 new ProcessBuilder(String cmd).start()

    都可以产生子进程对象Process。通过调用Process对象的waitFor()方法可以使主进程进入等待状态,直至子进程执行完毕,再进行下一步工作。如果对子进程处理不当,有可能造成主进程阻塞,整个程序死掉。

java Api中关于Process说的是:

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

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

    在对getOutputStream()getInputStream()getErrorStream()的描述中,有个注意事项:对其输出流和错误流进行缓冲是一个好主意!嗯,好抽象啊!

    问题正在于此,Process.getInputStream()和Process.getErrorStream()分别返回Process的标准输出流和错误流,两个流如果处理不当,其缓冲区不能被及时清除而被塞满,则进程被阻塞,即使调用Process.destory()也未必能销毁被阻塞的子进程。

    如果尝试同步获取Process的输出流和错误流进行处理,未必有效,顺序执行过程中,输出流和错误流常常不能得到及时处理。解决方案有两个。

    顺序执行的方案(对于stdout和stderr流信息会出现异常):
    private List<String> response;
    private List<String> error;
    public int executeCMD(String cmds, String decodeCharset)  
    {  
    logger.debug("Enter:cmds=" + cmds + ",decodeCharset" + decodeCharset);
        
    response = new ArrayList<>();
        error = new ArrayList<>();
    int exitValue = 0;
        
        BufferedReader br = null;
        try  
        {  
            Process p = Runtime.getRuntime().exec(cmds);
            logger.debug("start process");
            
            br = new BufferedReader(new InputStreamReader(p.getInputStream(),decodeCharset));  
            String line = null;  
            while ((line = br.readLine()) != null)  
            {  
                //response.append(line).append(System.lineSeparator());  
                logger.debug("line="+line);
                if(!line.trim().isEmpty())
                {
                    response.add(line);
                }
            }

            br.close();  
            br = new BufferedReader(new InputStreamReader(p.getErrInputStream(),decodeCharset));  
            String line = null;  
            while ((line = br.readLine()) != null)  
            {  
                //response.append(line).append(System.lineSeparator());  
                logger.debug("line="+line);
                if(!line.trim().isEmpty())
                {
                    error.add(line);
                }
            }
                        
            logger.debug("waiting for exit...");
            exitValue = p.waitFor();//use exitVlaue() may cause process not exit exception
        }catch (Exception e)  
        {       
            exitValue = -1;
            logger.error(e.getMessage());
            e.printStackTrace();
        }finally
        {  
            if (br != null)  
            {  
                try
                {
                    br.close();
                }catch (Exception e){
                }
            }
        }
        logger.debug("Return:exitValue:" + exitValue);   
        return exitValue;  
    } 

 

解决方案:

方案一:并发获取Process的输出流和错误流。

参考:

http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2

通过启动两个线程来并发地读取和处理输出流和错误流,懒得打开IDE了,就大概敲一下代码吧,可能有错误,如下:

调用者:

class ProcessExecutor  
{  
    private Process p;  
    private List<String> outputList;  
    private List<String> errorOutputList;  
    public ProcessExecutor(Process p) throws IOException  
    {  
        if(null == p)  
        {  
            throw new IOException("the provided Process is null");  
        }  
        this. p = p;  
    }  
    public List<String> getOutputList()  
    {  
        return this. outputList;  
    }  
    public List<String> getErrorOutputList()  
    {  
        return this.errorOutputList;  
    }  
    public int execute()  
    {  
        int rs = 0;  
        Thread errorOutputThread = new ProcessOutputThread(this.p.getErrorStream()); 
        Thread outputThread = new ProcessOutputThread(this.p.getInputStream());    
        //错误流必须在输出流之前读取,否则会导致流在读取过程中hang住。
        errorOutputThread.start();  
        outputThread.start();  
       
        rs = p.waitFor();  
        outputThread.join();  
        errorOutputThread.join();  
        this.outputList = outputThread.getOutputList();  
        this.errorOutputList = errorOutputThread.getOutputList();  
        return rs;  
    }  
}

流处理线程

class ProcessOutputThread  
{  
    private InputStream is;  
    private List<String> outputList;  
    public ProcessOutputThread(InputStream is) throws IOException  
    {  
        if(null == is)  
        {  
            throw new IOException("the provided InputStream is null");  
        }  
        this. is = is;  
        this.outputList = new ArrayList<String>();  
    }  
    public List<String> getOutputList()  
    {  
        return this. outputList;  
    }  
    @Override  
    public void run()  
    {  
        InputStreamReader ir = null;  
        BufferedReader br = null;  
        try  
        {  
            ir = new InputStreamReader(this.is);  
            br = new BufferedReader(ir);  
            String output = null;  
            while(null != (output = br.readLine()))  
            {  
                print(output);  
                this.outputList.add(output);  
            }  
        }  
        catch(IOException e)  
        {  
            e.print();  
        }  
        finally  
        (  
            try  
            {  
                if(null != br)  
                {  
                    br.close();  
                }  
                if(null != ir)  
                {  
                    ir.close();  
                }  
                if(null != this.is)  
                {  
                    this.is.close();  
                }  
            }  
            catch(IOException e)  
            {  
                e.print();  
            }  
        )  
    }  
}

方案二:(未曾实验过)用ProcessBuilder的redirectErrorStream()方法合并输出流和错误流。

参考:http://blog.csdn.net/dancen/article/details/7969328

public int execute()  
{  
    int rs = 0;  
    String[] cmds = {...};//command and arg    
    ProcessBuilder builder = new ProcessBuilder(cmds);    
    builder.redirectErrorStream(true);    
    Process process = builder.start();    
    BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));    
    String output = null;    
    while (null != (readLine = br.readLine()))  
    {    
        print(output);     
    }    
    rs = process.waitFor();  
    return rs;  
}

 

补充:通过RunTime生成进程的方法

        String[] cmd = new String[]
        {
            "D:\\Program Files (x86)\\HighGo\\Database\\1.3\\bin\\pg_ctl",
            "start",
            "-D",
            "D:\\Program Files (x86)\\HighGo\\Database\\1.3\\data"
        };
        java.lang.Process ps = Runtime.getRuntime().exec(cmd);
        try (Scanner scanner = new Scanner(ps.getInputStream()))
        {
            while (scanner.hasNextLine())
            {
                System.out.println(scanner.nextLine());
            }
        }
        System.out.println(""+ps.waitFor());
        ps.destroy();

 

转载于:https://my.oschina.net/liuyuanyuangogo/blog/418556

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值