java里执行linux命令,关于死锁的问题

最近在用Java调用ffmpeg的命令,因此记录下踩到的坑java

若是要在Java中调用shell脚本时,可使用Runtime.exec或ProcessBuilder.start。它们都会返回一个Process对象,经过这个Process能够对获取脚本执行的输出,而后在Java中进行相应处理。例如,下面的代码shell

try 
		{
			Process process = Runtime.getRuntime().exec(cmd);
			
			process.waitFor();
                        
                        //do something ...
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

一般,安全编码规范中都会指出:使用Process.waitfor的时候,可能致使进程阻塞,甚至死锁。 那么这句应该怎么理解呢?用个实际的例子说明下。安全

问题描述

         使用Java代码调用shell脚本,执行后会发现Java进程和Shell进程都会挂起,没法结束。Java代码 processtest.javabash

try 
		{
			Process process = Runtime.getRuntime().exec(cmd);
			System.out.println("start run cmd=" + cmd);
			
			process.waitFor();
			System.out.println("finish run cmd=" + cmd);
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

被调用的Shell脚本doecho.shide

#!/bin/bash
for((i=0; ;i++))
do    
    echo -n "0123456789"
    echo $i >> count.log
done

挂起缘由

  1.  主进程中调用Runtime.exec会建立一个子进程,用于执行shell脚本。子进程建立后会和主进程分别独立运行。
  2.  由于主进程须要等待脚本执行完成,而后对脚本返回值或输出进行处理,因此这里主进程调用Process.waitfor等待子进程完成。
  3.  经过shell脚本能够看出:子进程执行过程就是不断的打印信息。主进程中能够经过Process.getInputStream和Process.getErrorStream获取并处理。
  4.  这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,而后也会挂起。
  5. 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终致使死锁。

解决方法

        方案一: 基于上述分析,只要主进程在waitfor以前,能不断处理缓冲区中的数据就能够。由于,咱们能够再waitfor以前,单独启两个额外的线程,分别用于处理InputStream和ErrorStream就能够。实例代码以下:ui

try 
		{
			final Process process = Runtime.getRuntime().exec(cmd);
			System.out.println("start run cmd=" + cmd);
			
			//处理InputStream的线程
			new Thread()
			{
				@Override
				public void run()
				{
					BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); 
					String line = null;
					
					try 
					{
						while((line = in.readLine()) != null)
						{
							System.out.println("output: " + line);
						}
					} 
					catch (IOException e) 
					{						
						e.printStackTrace();
					}
					finally
					{
						try 
						{
							in.close();
						} 
						catch (IOException e) 
						{
							e.printStackTrace();
						}
					}
				}
			}.start();
			
			new Thread()
			{
				@Override
				public void run()
				{
					BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream())); 
					String line = null;
					
					try 
					{
						while((line = err.readLine()) != null)
						{
							System.out.println("err: " + line);
						}
					} 
					catch (IOException e) 
					{						
						e.printStackTrace();
					}
					finally
					{
						try 
						{
							err.close();
						} 
						catch (IOException e) 
						{
							e.printStackTrace();
						}
					}
				}
			}.start();
			
			process.waitFor();
			System.out.println("finish run cmd=" + cmd);
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

方案二:那就直接用ProcessBuilder来建立Process对象吧!ProcessBuilder已经给出了这方面的解决方案,可是必需要注意的是ProcessBuilder的redirectErrorStream方法。查API可知晓,redirectErrorStream方法设置为ture的时候,会将getInputStream(),getErrorStream()两个流合并,自动会清空流,无需咱们本身处理。若是是false,getInputStream(),getErrorStream()两个流分开,就必须本身处理,程序以下:编码

public class ProcessBuilderTest {
    public static void main(String[] args) {
        List<String> params = new ArrayList<String>();
        params.add("java");
        params.add("-jar");
        params.add("ProcessJar.jar");
        params.add("args1");
        params.add("args2");
        params.add("args3");

        ProcessBuilder processBuilder = new ProcessBuilder(params);
//        System.out.println(processBuilder.directory());
//        System.out.println(processBuilder.environment());
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            int exitCode = process.waitFor();
            System.out.println("exitCode = "+exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
相关文章
相关标签/搜索