java调用process线程阻塞问题

Posted 遗失的岁月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java调用process线程阻塞问题相关的知识,希望对你有一定的参考价值。

背景

项目需求中涉及java调用.bat文件进行图像处理,先直接上简略版程序

 1     public void draw(){
 2 
 3         //调用bat脚本进行图像处理
 4         Process process = null;
 5         InputStream in = null;
 6         try {
 7             process = Runtime.getRuntime().exec("startup.bat");
 8 
 9             //输出测试
10 //            in = process.getInputStream();
11 //            String line;
12 //            BufferedReader br = new BufferedReader(new InputStreamReader(in));
13 //            while ((line = br.readLine()) != null) {
14 //                System.out.println(line);
15 //            }
16 
17             //等待
18             process.waitFor();
19 
20         } catch (Exception e) {
21 
22         } finally {
23             process.destroy();
24         }
25     }

 

JAVA使用遇到的问题描述

  一般需要调用系统命令时,大部分人第一反应肯定是使用Runtime.getRuntime().exec(command)返回一个process对象,再调用process.waitFor()来等待命令执行结束,获取执行结果。

  调试的时候发现异常现象,process.waitFor();一直没有结束,导致线程阻塞再次,强行关闭程序后,发现图像处理只进行了一部分。

  根据现象并查看了JDK的帮助文档,如下

如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。

对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的阻塞,甚至死锁。

Process执行逻辑

* 主进程中调用Runtime.exec会创建一个子进程,用于执行脚本。子进程创建后会和主进程分别独立运行。

* 创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。

* 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。

*  这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

 

解决方法:在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;

 

修改后代码

 1 public class test {
 2 
 3     public void draw(){
 4 
 5         //调用bat脚本进行图像处理
 6         Process process = null;
 7         InputStream in = null;
 8         try {
 9             process = Runtime.getRuntime().exec("startup.bat");
10 
11             //输出测试
12 //            in = process.getInputStream();
13 //            String line;
14 //            BufferedReader br = new BufferedReader(new InputStreamReader(in));
15 //            while ((line = br.readLine()) != null) {
16 //                System.out.println(line);
17 //            }
18             //新启两个线程
19             new DealProcessSream(process.getInputStream()).start();
20             new DealProcessSream(process.getErrorStream()).start();
21 
22             process.waitFor();
23 
24         } catch (Exception e) {
25 
26         } finally {
27             process.destroy();
28         }
29     }
30 }

 

 1 public class DealProcessSream extends Thread {
 2     private InputStream inputStream;
 3 
 4     public DealProcessSream(InputStream inputStream) {
 5         this.inputStream = inputStream;
 6     }
 7 
 8     public void run() {
 9         InputStreamReader inputStreamReader = null;
10         BufferedReader br = null;
11         try {
12             inputStreamReader = new InputStreamReader(
13                     inputStream);
14             br = new BufferedReader(inputStreamReader);
15             // 打印信息
16 //            String line = null;
17 //            while ((line = br.readLine()) != null) {
18 //                System.out.println(line);
19 //            }
20             // 不打印信息
21            while (br.readLine() != null);
22         } catch (IOException ioe) {
23             ioe.printStackTrace();
24         }finally {
25             try {
26                 br.close();
27                 inputStreamReader.close();
28             } catch (IOException e) {
29                 e.printStackTrace();
30             }
31         }
32 
33     }
34 }

以上是关于java调用process线程阻塞问题的主要内容,如果未能解决你的问题,请参考以下文章

InputStream.close() 是java中的阻塞调用吗?

Java中是否父线程阻塞后子线程就无法继续执行?

python多进程之Process

java Future 阻塞

什么是Java方法线程唤醒与阻塞

Java 中你怎样唤醒一个阻塞的线程?