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();
        }
    }
}
相關文章
相關標籤/搜索