使用Runtime 调用Process.waitfor导致的阻塞问题

 更新时间:2021年12月14日 10:00:28   作者:弦上的梦  
这篇文章主要介绍了使用Runtime 调用Process.waitfor导致的阻塞问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

1. 关于Runtime类的小知识

  • Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一一个得到运行时环境的方法
  • Runtime中的exit方法是退出JVM

2. Runtime的几个重要的重载方法

方法名 作用
exec(String command); 在单独的进程中执行指定的字符串命令。
exec(String command, String[] envp) 在指定环境的单独进程中执行指定的字符串命令。
exec(String[] cmdarray, String[] envp, File dir) 在指定环境和工作目录的独立进程中执行指定的命令和变量
exec(String command, String[] envp, File dir) 在有指定环境和工作目录的独立进程中执行指定的字符串命令。

Runtime类的重要的方法还有很多,简单列举几个

  • exit(int status):终止当前正在运行的 Java 虚拟机
  • freeMemory():返回 Java 虚拟机中的空闲内存量。
  • load(String filename): 加载作为动态库的指定文件名。
  • loadLibrary(String libname): 加载具有指定库名的动态库。

3. Runtime的使用方式

错误的使用exitValue()

  public static void main(String[] args) throws IOException {
        String command = "ping www.baidu.com";
        Process process = Runtime.getRuntime().exec(command);
        int i = process.exitValue();
        System.out.println("字进程退出值:"+i);
    }

输出:

Exception in thread "main" java.lang.IllegalThreadStateException: process has not exited
at java.lang.ProcessImpl.exitValue(ProcessImpl.java:443)
at com.lirong.think.runtime.ProcessUtils.main(ProcessUtils.java:26)

原因:

exitValue()方法是非阻塞的,在调用这个方法时cmd命令并没有返回所以引起异常。阻塞形式的方法是waitFor,它会一直等待外部命令执行完毕,然后返回执行的结果。

修改后的版本:

  public static void main(String[] args) throws IOException {
        String command = "javac";
        Process process = Runtime.getRuntime().exec(command);
        process.waitFor();
        process.destroy();
        int i = process.exitValue();
        System.out.println("字进程退出值:"+i);
    }

此版本已然可以正常运行,但当主线程和子线程有很多交互的时候还是会出问题,会出现卡死的情况。

4. 卡死原因

  • 主进程中调用Runtime.exec会创建一个子进程,用于执行cmd命令。子进程创建后会和主进程分别独立运行。
  • 因为主进程需要等待脚本执行完成,然后对命令返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。
  • 运行此cmd命令可以知道:子进程执行过程就是打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。
  • 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
  • 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

5. 解决方案

不断的读取消耗缓冲区的数据,以至子进程不会挂起,下面是具体代码:

/**
 * @author lirong
 * @desc CMD命令测试
 * @date 2019/06/13 20:50
 */
@Slf4j
public class ProcessUtils {
    public static void main(String[] args) throws IOException, InterruptedException {
        String command = "ping www.baidu.com";
        Process process = Runtime.getRuntime().exec(command);
        readStreamInfo(process.getInputStream(), process.getErrorStream());
        int exit = process.waitFor();
        process.destroy();
        if (exit == 0) {
            log.debug("子进程正常完成");
        } else {
            log.debug("子进程异常结束");
        }
    }
    /**
     * 读取RunTime.exec运行子进程的输入流 和 异常流
     * @param inputStreams 输入流
     */
    public static void readStreamInfo(InputStream... inputStreams){
        ExecutorService executorService = Executors.newFixedThreadPool(inputStreams.length);
        for (InputStream in : inputStreams) {
            executorService.execute(new MyThread (in));
        }
        executorService.shutdown();
    }
}
/**
 * @author lirong
 * @desc
 * @date 2019/06/13 21:25
 */
@Slf4j
public class MyThread implements Runnable {
    private InputStream in;
    public MyThread(InputStream in){
        this.in = in;
    }
    @Override
    public void run() {
        try{
            BufferedReader br = new BufferedReader(new InputStreamReader(in, "GBK"));
            String line = null;
            while((line = br.readLine())!=null){
                log.debug(" inputStream: " + line);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

写到这里大家以为都结束了哇,并没有,哈哈哈,真实的生成环境总能给你带来很多神奇的问题,Runtime不仅可以直接调用CMD执行命令,还可以调用其他.exe程序执行命令。

所以纵使你读取了缓冲区的数据,你的程序依然可能会被卡死,因为有可能你的缓冲区根本就没有数据,而是你的.exe程序卡主了。嗯,所以为你以防万一,你还需要设置超时。

6. Runtime最优雅的调用方式

/**
 * @author lirong
 * @desc
 * @date 2019/06/13 20:50
 */
@Slf4j
public class ProcessUtils {
   /**
     * @param timeout 超时时长
     * @param fileDir 所运行程序路径
     * @param command 程序所要执行的命令
     * 运行一个外部命令,返回状态.若超过指定的超时时间,抛出TimeoutException
     */
    public static int executeProcess(final long timeout, File fileDir, final String[] command)
            throws IOException, InterruptedException, TimeoutException {
        Process process = Runtime.getRuntime().exec(command, null, fileDir);
        Worker worker = new Worker(process);
        worker.start();
        try {
            worker.join(timeout);
            if (worker.exit != null){
                return worker.exit;
            } else{
                throw new TimeoutException();
            }
        } catch (InterruptedException ex) {
            worker.interrupt();
            Thread.currentThread().interrupt();
            throw ex;
        }
        finally {
            process.destroy();
        }
    }
    
    private static class Worker extends Thread {
        private final Process process;
        private Integer exit;
        private Worker(Process process) {
            this.process = process;
        }
        @Override
        public void run() {
            InputStream errorStream = null;
            InputStream inputStream = null;
            try {
                errorStream = process.getErrorStream();
                inputStream = process.getInputStream();
                readStreamInfo(errorStream, inputStream);
                exit = process.waitFor();
                process.destroy();
                if (exit == 0) {
                    log.debug("子进程正常完成");
                } else {
                    log.debug("子进程异常结束");
                }
            } catch (InterruptedException ignore) {
                return;
            }
        }
    }
    /**
     * 读取RunTime.exec运行子进程的输入流 和 异常流
     * @param inputStreams 输入流
     */
    public static void readStreamInfo(InputStream... inputStreams){
        ExecutorService executorService = Executors.newFixedThreadPool(inputStreams.length);
        for (InputStream in : inputStreams) {
            executorService.execute(new MyThread(in));
        }
        executorService.shutdown();
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java的编译时错误和运行时错误问题

    Java的编译时错误和运行时错误问题

    这篇文章主要介绍了Java的编译时错误和运行时错误问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 详解MybatisPlus中@Version注解的使用

    详解MybatisPlus中@Version注解的使用

    在MyBatisPlus中,常常使用@Version实现乐观锁,该注解用于字段上面。本文将通过示例详细讲解@Version注解的使用,感兴趣的可以了解一下
    2022-06-06
  • Spring Bean注册与注入实现方法详解

    Spring Bean注册与注入实现方法详解

    首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入。依赖注入是指:让调用类对某一接口的实现类的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖。Spring容器中支持的依赖注入方式主要有属性注入、构造函数注入、工厂方法注入
    2022-10-10
  • Java中队列Queue和Deque的区别与代码实例

    Java中队列Queue和Deque的区别与代码实例

    学过数据结构的,一定对队列不陌生,java也实现了队列,下面这篇文章主要给大家介绍了关于Java中队列Queue和Deque区别的相关资料,需要的朋友可以参考下
    2021-08-08
  • SpringBoot Shiro授权实现过程解析

    SpringBoot Shiro授权实现过程解析

    这篇文章主要介绍了SpringBoot Shiro授权实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java多线程常见案例分析线程池与单例模式及阻塞队列

    Java多线程常见案例分析线程池与单例模式及阻塞队列

    这篇文章主要介绍了多线程的常见案例,线程池(重点)、单例模式、阻塞队列,本文通过图文实例相结合给大家介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • 深入浅析hbase的优点

    深入浅析hbase的优点

    本文讲述了HBase的特征和它的优点,并简要回顾了行键设计的重点之处,它还向你展示了如何在本地配置HBase环境,使用命令创建表、插入数据、检索指定行以及最后如何进行scan操作,感兴趣的朋友一起看看吧
    2017-09-09
  • Mybatis-plus动态条件查询QueryWrapper的使用案例

    Mybatis-plus动态条件查询QueryWrapper的使用案例

    mybatis-plus框架功能很强大,把很多功能都集成了,下面这篇文章主要给大家介绍了关于Mybatis-plus动态条件查询QueryWrapper的使用教程,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    这篇文章主要介绍了IDEA配置静态资源热加载操作(Springboot修改静态资源不重启),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • JAVA流控及超流控后的延迟处理实例

    JAVA流控及超流控后的延迟处理实例

    这篇文章主要介绍了JAVA流控及超流控后的延迟处理,以实例形式较为详细的分析了Java进行流量控制的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2014-12-12

最新评论