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

最近在用Java调用ffmpeg的命令,所以记录下踩到的坑

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

try

{

Process process = Runtime.getRuntime().exec(cmd);

process.waitFor();

//do something ...

}

catch (Exception e)

{

e.printStackTrace();

}

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

问题描述

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

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.sh

#!/bin/bash

for((i=0; ;i++))

do

echo -n "0123456789"

echo $i >> count.log

done

挂起原因

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

因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。

通过shell脚本可以看出:子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。

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

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

解决方法

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

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 params = new ArrayList();

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();

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值