Java Process 的 waitFor()

本文详细介绍了如何在Java中使用Runtime和ProcessBuilder启动ffmpeg程序,并通过创建两个线程分别读取ffmpeg程序的标准输出流和标准错误流以解决调用waitFor()方法导致的阻塞问题。文中还提供了具体的代码示例,以帮助开发者避免在实际应用中遇到类似问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序:

     (1) 使用Runtime的exec()方法

     (2) 使用ProcessBuilder的start()方法

       Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

      来说说我遇到的实际情况:我想调用ffmpeg程序来对一首歌曲进行转码,把高音质版本的歌曲转为多种低码率的文件。但是在转码完成之后需要做以下操作:读取文件大小,写入ID3信息等。这时我们就想等转码操作完成之后我们可以知道。

如下这样代码

1 Process p = null
2 try 
3     p = Runtime.getRuntime().exec("notepad.exe"); 
4 catch (Exception e) { 
5     e.printStackTrace(); 
6
7 System.out.println("我想被打印...");


在notepad.exe被执行的同时,打印也发生了,但是我们想要的是任务完成之后它才被打印。


之后发现在Process类中有一个waitFor()方法可以实现。如下:

1 Process p = null
2 try 
3     p = Runtime.getRuntime().exec("notepad.exe"); 
4     p.waitFor(); 
5 catch (Exception e) { 
6     e.printStackTrace(); 
7
8 System.out.println("我想被打印...");

 这下又出现了这样的现象,必须要等我们把记事本关闭, 打印语句才会被执行。并且你不能手动关闭它那程序就一直不动,程序貌似挂了.....这是什么情况,想调用个别的程序有这么难吗?让我们来看看waitFor()的说明:


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,

        问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。

        接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接标准输入标准输出标准错误流

 

假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

01 Runtime rt = Runtime.getRuntime(); 
02 String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile; 
03 try 
04  p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin")); 
05  //获取进程的标准输入流 
06  final InputStream is1 = p.getInputStream();  
07  //获取进城的错误流 
08  final InputStream is2 = p.getErrorStream(); 
09  //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流 
10  new Thread() { 
11     public void run() { 
12        BufferedReader br1 = new BufferedReader(new InputStreamReader(is1)); 
13         try 
14             String line1 = null
15

            while ((line1 = br1.readLine()) != null) { 

                  System.out.println(line1);//打印日志

16                   if (line1 != null){} 
17               
18         catch (IOException e) { 
19              e.printStackTrace(); 
20         
21         finally
22              try 
23                is1.close(); 
24              catch (IOException e) { 
25                 e.printStackTrace(); 
26             
27           
28         
29      }.start(); 
30                                  
31    new Thread() {  
32       public void  run() {  
33        BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));  
34           try {  
35              String line2 = null ;  
36              while ((line2 = br2.readLine()) !=  null ) {  
37                   if (line2 != null){} 
38              }  
39            catch (IOException e) {  
40                  e.printStackTrace(); 
41            }  
42           finally
43              try 
44                  is2.close(); 
45              catch (IOException e) { 
46                  e.printStackTrace(); 
47              
48            
49         }  
50       }.start();   
51                                  
52       p.waitFor(); 
53       p.destroy();  
54      System.out.println("我想被打印..."); 
55     catch (Exception e) { 
56             try
57                 p.getErrorStream().close(); 
58                 p.getInputStream().close(); 
59                 p.getOutputStream().close(); 
60                 
61              catch(Exception ee){} 
62           
63    }

这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。但是在其中过程中我却发现真正起关键作用的缓冲区是getErrorStream()所对应的那个缓冲区没有被清空,意思就是说其实只要及时读取标准错误流缓冲区的数据程序就不会被block


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值