• 【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来存储.
    代码目前运行稳定, 起码好几个月了.

    点赞~

    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    x-pack-crack
    ELK获取用户真实IP
    多层代理获取用户真实IP
    ELK帮助文档
    logstash filter plugin
    开源实时日志分析ELK平台部署
    消息队列集群kafka安装配置
    日志采集客户端 filebeat 安装部署
    rsync + inotify 同步
    【转】OpenWrt 防火墙/etc/config/firewall介绍
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/13615457.html
Copyright © 2020-2023  润新知