解决Process.getInputStream()阻塞的问题

Process.getInputStream()阻塞问题

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的输出流和错误流进行处理,未必有效,顺序执行过程中,输出流和错误流常常不能得到及时处理。解决方案有两个。

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

通过启动两个线程来并发地读取和处理输出流和错误流,懒得打开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 outputThread = new ProcessOutputThread(this.p.getInputStream());
  Thread errorOutputThread = new ProcessOutputThread(this.p.getErrorStream());
  outputThread.start();
  errorOutputThread.start();
  rs = p.waitFor();
  outputThread.join();
  errorOutputThread.join();
  this.outputList = outputThread.getOutputList();
  this.errorOutputList = errorOutputThread.getOutputList();
  return rs;
 }
}

流处理线程

class ProcessOutputThread extends Thread
{
 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()方法合并输出流和错误流

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;
} 

Java Process 阻塞测试总结

Process阻塞原因:输入流和错误流分开的,没有处理,就会发生阻塞,归根结底本质上是bio引起的io阻塞问题。

getInputStream,getErrorSteam就是获取脚本或者命令的控制台回显信息,前者获取的是标准输出的回显信息,后者获取的是标准错误的回显信息

Process原理:使用Runtime.getRuntime().exec(cmd)会在当前进程建立一个子进程,子进程由于没有控制台,它的标准输出和标准错误就会返回给父进程Process,因此通过getInputStream和getErrorStream就可以获取到这些信息。

测试代码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class JavaExeBat {
        public JavaExeBat() {
        }
        public static void main(String[] args) {
                Process p;
                //test.bat中的命令是ipconfig/all
                String cmd="sh test.sh ";
                //String cmd="ping 127.0.0.1 -c 4";

                try {
                        //执行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令结果的输出流
                        //输出流
                        InputStream fis=p.getInputStream();
                        //错误流
                        InputStream ferrs=p.getErrorStream();
                        //用一个读输出流类去读
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        //用缓冲器读行
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        //直到读完为止
                        while((line=br.readLine())!=null) {
                        //有可能发生阻塞的问题
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能发生阻塞的问题
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}

test.sh如下

#!/bin/bash

for((i=0; i < 100000; i++));do
         //输出的标准输出
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        //输出到标准错误
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

经过测试发现,如果JavaExeBat.java文件中只开启标准输出或者标准错误时,进程就会夯住,无法通过waiteFor获取其返回值,因为脚本中分别输出了100000w条信息到标准输出和标准错误,而下述代码只处理了getInputStream,导致标准错误输出流的信息太多返回给当前进程,没有得到处理,因此阻塞。

代码如下:

p = Runtime.getRuntime().exec(cmd);
                        //取得命令结果的输出流
                        //输出流
                        InputStream fis=p.getInputStream();
                        //用一个读输出流类去读
                        InputStreamReader isr=new InputStreamReader(fis);
                        //用缓冲器读行
                        BufferedReader br=new BufferedReader(isr);
                        String line=null;
                        //直到读完为止
                        while((line=br.readLine())!=null) {
                        //有可能发生阻塞的问题
                                System.out.println("return input Str:" + line);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);

把上述代码中的getInputStream换做getErrorStream,也会夯住进程,因为同样只处理了两者中一者,即标准错误。

那么能不能同步处理两个流信息呢?代码如下:

try {
                        //执行命令
                        p = Runtime.getRuntime().exec(cmd);
                        //取得命令结果的输出流
                        //输出流
                        InputStream fis=p.getInputStream();
                        //错误流
                        InputStream ferrs=p.getErrorStream();
                        //用一个读输出流类去读
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        //用缓冲器读行
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        //直到读完为止
                        while((line=br.readLine())!=null) {
                        //有可能发生阻塞的问题
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        //有可能发生阻塞的问题
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

测试过后发现也不行,因为是同步的,就会有先后顺序,也会发生阻塞,测试方法,将test.sh改为只打印标准错误,就会发现标准错误处理被阻塞,脚本如下:

#!/bin/bash

for((i=0; i < 100000; i++));do
        //输出到标准错误
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

解决办法思路:

(1)并发处理两个流信息,开启两个线程分别处理输出流与错误流

(2)将两个流合并为一个流解决示例:

第一种思路:

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 outputThread = new ProcessOutputThread(this.p.getInputStream());
            Thread errorOutputThread = new ProcessOutputThread(this.p.getErrorStream());
            outputThread.start();
            errorOutputThread.start();
            rs = p.waitFor();
            outputThread.join();
            errorOutputThread.join();
            this.outputList = outputThread.getOutputList();
            this.errorOutputList = errorOutputThread.getOutputList();
            return rs;
        }
    }  

    class ProcessOutputThread extends Thread
    {
        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(true);将输出流与错误流合并

 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;
    }  

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java 打造阻塞式线程池的实例详解

    java 打造阻塞式线程池的实例详解 原来以为tiger已经自带了这种线程池,就是在任务数量超出时能够阻塞住投放任务的线程,主要想用在JMS消息监听. 开始做法: 在ThreadPoolExcecutor中代入new ArrayBlockingQueue(MAX_TASK). 在任务超出时报错:RejectedExecutionException. 后来不用execute方法加入任务,直接getQueue().add(task), 利用其阻塞特性.但是发现阻塞好用了,但是任务没有被处理.一看Qu

  • java阻塞队列实现原理及实例解析

    这篇文章主要介绍了java阻塞队列实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 阻塞队列与普通队列的不同在于.当队列是空的时候,从队列中获取元素的操作将会被阻塞,或者当队列满时,往队列里面添加元素将会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素.同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完

  • 处理java异步事件的阻塞和非阻塞方法分析

    前言 由于多核系统普遍存在,并发性编程的应用无疑比以往任何时候都要广泛.但并发性很难正确实现,用户需要借助新工具来使用它.很多基于 JVM 的语言都属于这类开发工具,Scala 在这一领域尤为活跃.本系列文章将介绍一些针对 Java 和 Scala 语言的较新的并发性编程方法. 在任何并发性应用程序中,异步事件处理都至关重要.事件来源可能是不同的计算任务.I/O 操作或与外部系统的交互.无论来源是什么,应用程序代码都必须跟踪事件,协调为响应事件而采取的操作. Java 应用程序可采用两种基本的异

  • Java 进程执行外部程序造成阻塞的一种原因

    查了好多资料,差点就动手翻Java源码了,最后结合一篇文章(忘记出处了),想到了输出流会阻塞进程执行. Java进程执行有一个输入流,两个输出流(相对于外部程序).当两个输出流有内容输出,而Java执行程序没有及时清空输出流时就会阻塞进程. 现贴出代码,希望能帮助到有需要的同行: 复制代码 代码如下: /**  * pdf转swf函数  * @param path 输入输出文件路径  * @param inputFileName 输入文件名  * @param outputFileName 输出

  • 解决Process.getInputStream()阻塞的问题

    Process.getInputStream()阻塞问题 Java中 Runtime.getInstance().exec (String cmd) 或者 new ProcessBuilder(String cmd).start() 都可以产生子进程对象Process.通过调用Process对象的waitFor()方法可以使主进程进入等待状态,直至子进程执行完毕,再进行下一步工作.如果对子进程处理不当,有可能造成主进程阻塞,整个程序死掉. java Api中关于Process说的是: Proce

  • java调用process线程阻塞问题的解决

    java调用process线程阻塞问题 项目需求中涉及java调用.bat文件进行图像处理,先直接上简略版程序 public void draw(){ //调用bat脚本进行图像处理 Process process = null; InputStream in = null; try { process = Runtime.getRuntime().exec("startup.bat"); //输出测试 // in = process.getInputStream(); // Stri

  • 基于Process#waitFor()阻塞问题的解决

    目录 Process#waitFor()阻塞问题 Process.waitFor()导致主线程堵塞 Process#waitFor()阻塞问题 有时需要在程序中调用可执行程序或脚本命令: Process process = Runtime.getRuntime().exec(shPath); int exitCode = process .waitFor(); Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执

  • 解决Java中socket使用getInputStream()阻塞问题

    目录 socket使用getInputStream()阻塞 用线程解决Socket的getInputStream阻塞 1.背景 2.问题 3.原因 4.解决办法 5.Socket通信注意事项 socket使用getInputStream()阻塞 今天用socket进行编程练习时,发现程序到了getInputStream()这里就进行不下去了 Socket socket = new Socket("127.0.0.1", 800); ObjectInputStream reader =

  • Python使用monkey.patch_all()解决协程阻塞问题

    直接参考以下实例,采用协程访问三个网站 由于IO操作非常耗时,程序经常会处于等待状态 比如请求多个网页有时候需要等待,gevent可以自动切换协程 遇到阻塞自动切换协程,程序启动时执行monkey.patch_all()解决 # 由于IO操作非常耗时,程序经常会处于等待状态 # 比如请求多个网页有时候需要等待,gevent可以自动切换协程 # 遇到阻塞自动切换协程,程序启动时执行monkey.patch_all()解决 # 首行添加下面的语句即可 from gevent import monke

  • Java基于Runtime调用外部程序出现阻塞的解决方法

    本文实例讲述了Java基于Runtime调用外部程序出现阻塞的解决方法, 是一个很实用的技巧.分享给大家供大家参考.具体分析如下: 有时候在java代码中会调用一些外部程序,比如SwfTools来转换swf.ffmpeg来转换视频等.如果你的代码这样写:Runtime.getRuntime().exec(command),会发现程序一下就执行完毕,而在命令行里要执行一会,是因为java没有等待外部程序的执行完毕,此时就需要使用阻塞,来等待外部程序执行结果: InputStream stderr

  • Java Process与Runtime()的使用及调用cmd命令阻塞的解决方案

    Java Process与Runtime()使用 java调用cmd执行bat文件有时会出现卡死的现象,当时感觉很迷惑,后来查资料,本来一般都是这样来调用程序并获取进程的输出流的,但是我在windows上执行这样的调用的时候却总是在while那里被堵塞了,结果造成ffmpeg程序在执行了一会后不再执行,这里从官方的参考文档中我们可以看到这是由于缓冲区的问题,由于java进程没有清空ffmpeg程序写到缓冲区的内容,结果导致ffmpeg程序一直在等待. 在网上也查找了很多这样的问题,不过说的都是使

  • 调用Process.waitfor导致的进程挂起问题及解决

    目录 问题背景 问题描述 挂起原因 解决方法 JDK上的说明 背后的故事 测试pipe的大小 Linux上pipe分析 问题背景 如果要在Java中调用shell脚本时,可以使用Runtime.exec或ProcessBuilder.start.它们都会返回一个Process对象,通过这个Process可以对获取脚本执行的输出,然后在Java中进行相应处理. 例如,下面的代码: try { Process process = Runtime.getRuntime().exec(cmd); pro

  • 使用Runtime 调用Process.waitfor导致的阻塞问题

    目录 1. 关于Runtime类的小知识 2. Runtime的几个重要的重载方法 3. Runtime的使用方式 4. 卡死原因 5. 解决方案 6. Runtime最优雅的调用方式 1. 关于Runtime类的小知识 Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一一个得到运行时环境的方法 Runtime中的exit方法是退出JVM 2. Runtime的几个重要的重载方法 方法名 作用 exec(String command); 在单独的进程中

  • Java编程使用Runtime和Process类运行外部程序的方法

    本文实例讲述了Java编程使用Runtime和Process类运行外部程序的方法.分享给大家供大家参考,具体如下: 使用Runtime.getRuntime().exec()方法可以在java程序里运行外部程序. 1. exec(String command) 2. exec(String command, String envp[], File dir) 3. exec(String cmd, String envp[]) 4. exec(String cmdarray[]) 5. exec(

随机推荐