【Java多线程07】 并发安全读取Shell脚本/命令的输出的INFO流和ERR流

需求

使用Java执行一个shell脚本或者一系列shell命令, 网上简单的教程大多存在问题: INFO流和ERR流长长是由同一个线程来操作,不能同时读取或者关闭异常,无法自动终止——导致每次Java端调完脚本后,长时间阻塞在流的读取终止中。
定位问题: 取消 INFO流和ERR流的读取后,正常返回。

改造及核心代码如下

注释在代码里

继承Thread类 重写run方法,在run方法中读取日志流

public class RunThread extends Thread {
    private InputStream stream;
    private String printType;
    /**
     * 存储info或者error信息
     */
    private List<String> msgs;

    public RunThread(InputStream inputStream, String printType, List<String> msgs) {
        this.stream = inputStream;
        this.printType = printType;
        this.msgs = msgs;
    }

    @Override
    public void run() {
        try {
            BufferedReader br;
            try (InputStreamReader isr = new InputStreamReader(stream)) {
                br = new BufferedReader(isr);

                String line = null;
                while ((line = br.readLine()) != null) {
                    msgs.add(printType + ">" + line);
                }
            }
        } catch (Exception e) {
            msgs.add("RunThread执行发生异常:" + e.getMessage());
        }
    }
}

执行shell脚本的方法

    /**
     * 执行shell脚本
     *
     * @param filePath   要运行的脚本所在的目录; 当然你也可以把要运行的脚本写成全路径。
     * @return
     */
    public static  List<String> processShellFile(String filePath) throws Exception {
        // 声明返回信息的集合 retMsgList, 使用同步安全的集合类型即可; String  
        List<String> retMsgList = Collections.synchronizedList(new ArrayList<>(8));
        log.info("远程脚本开始执行,filePath=" + filePath);
        Process process = Runtime.getRuntime().exec(filePath);

        try (BufferedReader input = new BufferedReader(
                new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8")));
             BufferedReader error = new BufferedReader(
                     new InputStreamReader(process.getErrorStream(), Charset.forName("UTF-8")));) {

            List<String> infoList = new ArrayList<>(8);
            // 开启info流读取的线程
            new RunThread(process.getInputStream(), "INFO", retMsgList).start();
            List<String> errList = new ArrayList<>(8);
            // 开启error流读取的线程
            new RunThread(process.getErrorStream(), "ERR", retMsgList).start();

            int exitValue = process.waitFor();

            if (exitValue == 0) {
                retMsgList.add("exitValue == 0, successfully executed the envs command");
            } else {
                retMsgList.add("exitValue=" + exitValue + ", failure executed the envs command");
            }
        } catch (Exception e) {
            retMsgList.add("远程脚本 Error:" + e.getMessage());
            throw new Exception("远程脚本 Error,filePath=" + filePath, e);
        } finally {
            if (process != null) {
                Thread.sleep(10);
                process.destroy();
            }
        }
        return retMsgList;
    }

简单测试

    /**
     * 执行一条python命令 python /data/scm/scm_main_plan.py  DRP 110
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        System.out.println(processShellFile("E:\\PycharmProjects\\python-demo-learning\\day-0707-xxl\\dev002.py"));
    }

注意点

异常信息的格式可以自己指定, 我这里为了方便我的业务故用List来存储.
代码目前运行稳定, 起码好几个月了.

点赞~

posted @ 2020-09-04 18:20  山枫叶纷飞  阅读(358)  评论(0编辑  收藏  举报