[Java]_[初级]_[如何调用外部命令获取输出并设置它的超时退出]

Posted infoworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java]_[初级]_[如何调用外部命令获取输出并设置它的超时退出]相关的知识,希望对你有一定的参考价值。

场景

  1. 在开发Java程序的时候,有时候需要执行外部命令以获取特定信息,但是外部命令执行可能由于某些原因(比如CPU,内存占用高后延迟执行)比正常执行的时间要长,有时候甚至挂起阻塞了Java线程的执行。那么我们如何使用标准库来完成调用外部命令的超时时间设置?

  2. 还有基本的问题,如何读取启动进程的输出?

说明

  1. Java使用ProcessBuilder来创建外部命令的进程,并使用方法command来异步执行外部命令.
ProcessBuilder ps = new ProcessBuilder();
ps.command(命令路径);
Process process = ps.start();
  1. 之后通过对象processprocess.getInputStream()获取一个连接到进程输出的输入流,这个输入流使用管道的方式连接到进程的标准输出上,所以读取这个输入流就可以读取这个进程的输出。通过读取这个进程的输出并存储到StringBuffer里.
StringBuffer line = new StringBuffer();
String temp = "";
try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
    while ((temp = in.readLine()) != null)
        line.append(temp);

    exitCode = process.waitFor();
catch (Exception e)
    e.printStackTrace();
finally 
    if(exitCode != 0)
        process.destroy();

  1. 为了外部进程的超时判断,那么外部进程肯定需要额外的线程进行运行和读取输出,使用了线程池ThreadPoolExecutor来执行任务会比较好, 避免频繁的创建线程。而Executor.submit返回了一个Future对象,这个对象是并发包里的类,主要是可以异步获取工作线程返回的值。通过这个Future对象可以获取到外部进程执行的结果,它还有设置延时等待的get方法正好合适。
String java.util.concurrent.Future.get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
  1. 在使用Futureget获取返回结果时,可以通过抛出的超时异常来销毁未结束的进程。关于Future的介绍可以参考课程JDK8-Java8-JavaSE8新特性和增强功能.
TaskCallable callable = new TaskCallable();
Future<String> handler = executor.submit(callable);
String message = "";
try 
    message = handler.get(kGenWaitTimeoutSeconds, TimeUnit.SECONDS);
 catch (InterruptedException e) 
    e.printStackTrace();
 catch (ExecutionException e) 
    e.printStackTrace();
 catch (TimeoutException e) 
    callable.cancelProcess();

  1. 在以下的例子里我把调用外部命令封装到一个Callable<String>对象里,方便Executor.submit调用.

例子

package com;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.junit.jupiter.api.Test;

public class ProcessExecTest 

    private ThreadPoolExecutor executor;
    private BlockingQueue<Runnable>queue;

    private static int queueSize = 1024;
    private static int threadKeepAliveTime = 120;
    private static int kGenWaitTimeoutSeconds = 10;
    
    public static void print(String key,String str)
        System.out.println(key+": "+str);
    

    public ProcessExecTest()
        int processors = Runtime.getRuntime().availableProcessors();

        // 常规任务
        queue = new LinkedBlockingQueue<Runnable>(queueSize);
        executor = new ThreadPoolExecutor(processors,processors,
                threadKeepAliveTime,TimeUnit.SECONDS, queue);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
    

    @Test
    public void testProcess()
        TaskCallable callable = new TaskCallable();
        Future<String> handler = executor.submit(callable);
        String message = "";
        try 
            message = handler.get(kGenWaitTimeoutSeconds, TimeUnit.SECONDS);
         catch (InterruptedException e) 
            e.printStackTrace();
         catch (ExecutionException e) 
            e.printStackTrace();
         catch (TimeoutException e) 
            callable.cancelProcess();
        
        
        print("message",message);
    

    public static class TaskCallable implements Callable<String>

        private Process process;
        private int exitCode = 1;

        public void cancelProcess()
            if(process != null && exitCode != 0)
                process.destroy();
        

        public TaskCallable()
        

        @Override
        public String call() throws Exception 

            File dir = new File("C:\\\\Windows\\\\System32");
            ProcessBuilder ps = new ProcessBuilder();
            ps.directory(dir);
            Path dirPath = dir.toPath();
            Path exePath = dirPath.resolve("cmd.exe");

            ps.command(exePath.toString(),"/C","echo tobey");
            process = ps.start();
            if(process == null)
                return "";

            StringBuffer line = new StringBuffer();
            String temp = "";
            try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
                while ((temp = in.readLine()) != null)
                    line.append(temp);

                exitCode = process.waitFor();
            catch (Exception e)
                e.printStackTrace();
            finally 
                if(exitCode != 0)
                    process.destroy();
            
            return  (exitCode == 0)?line.toString():"";
        
    


输出

message: tobey

参考

  1. java - ExecutorService that interrupts tasks after a timeout - Stack Overflow

以上是关于[Java]_[初级]_[如何调用外部命令获取输出并设置它的超时退出]的主要内容,如果未能解决你的问题,请参考以下文章

[Android]_[初级]_[AndroidStudio编译输出中文乱码]

Linux初级知识_01 -- 基础命令

力扣_初级算法_链表_1~6题

如何调用外部程序并从另一个程序获取其输出

01_JNI是什么,为什么使用,怎么用JNI,Cygwin环境变量配置,NDK案例(使用Java调用C代码),javah命令使用

Jython 2.5.1:从 Java 调用到 __main__ - 如何传入命令行参数?