• 计算一个服务端方法运行多次的平均耗时(Java)


    做服务端初步性能测试时,往往需要将一个方法顺序或并发运行 N 次,计算最大、最小、平均耗时。

    话不多说,上代码。

    
    package sample.performance;
    
    import java.util.function.Consumer;
    
    /**
     * 消费器包装
     * Created by qinshu on 2021/7/11
     */
    public class ConsumerWrapper<T> {
    
        private Consumer<T> consumer;
        private T param;
    
        public ConsumerWrapper(Consumer<T> consumer, T param) {
            this.consumer = consumer;
            this.param = param;
        }
    
        public void run() {
            consumer.accept(param);
        }
    }
    
    
    
    public class PerformanceTestFramework {
    
        public static final Integer DATA_SIZE = 100;
        public static final Integer RUNTIMES = 1;
    
        public static final Integer THREAD_NUMS = 1;
    
        static List<Long> costs = new CopyOnWriteArrayList<>();
        static int errorCount = 0;
    
        private static ThreadPoolExecutor executorService = new ThreadPoolExecutor(8, 8, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1024));
    
        public static TestStrategyEnum SEQUENTIAL = TestStrategyEnum.SEQUENTIAL;
        public static TestStrategyEnum CONCURRENT = TestStrategyEnum.CONCURRENT;
    
        private static TestStrategy sequentialStrategy = new SeqTestStrategy();
        private static TestStrategy concurrentStrategy = new ConcurrentTestStrategy();
    
        public static void test(ConsumerWrapper consumerWrapper, TestStrategyEnum strategyEnum) {
            choose(strategyEnum).test(consumerWrapper);
        }
    
        private static TestStrategy choose(TestStrategyEnum strategyEnum) {
            return strategyEnum == TestStrategyEnum.SEQUENTIAL ? sequentialStrategy : concurrentStrategy;
        }
    
        enum TestStrategyEnum {
            SEQUENTIAL,
            CONCURRENT;
        }
    
    
        static class ConcurrentTestStrategy implements TestStrategy {
    
            @Override
            public void test(ConsumerWrapper consumerWrapper) {
    
                reset();
    
                CountDownLatch cdl = new CountDownLatch(RUNTIMES);
                for (int i=0; i < RUNTIMES; i++) {
                    executorService.submit(() -> {
                        time(consumerWrapper);
                        cdl.countDown();
                    });
                }
                try {
                    cdl.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                executorService.shutdown();
                executorService.shutdownNow();
    
                report(this.getClass().getName());
            }
        }
    
        static class SeqTestStrategy implements TestStrategy {
    
            @Override
            public void test(ConsumerWrapper consumerWrapper) {
    
                reset();
    
                for (int i=0; i < RUNTIMES; i++) {
                    time(consumerWrapper);
                }
    
                report(this.getClass().getName());
            }
        }
    
        interface TestStrategy {
            void test(ConsumerWrapper consumerWrapper);
        }
    
        private static void time(ConsumerWrapper consumerWrapper) {
            long start = System.currentTimeMillis();
            consumerWrapper.run();
            long end = System.currentTimeMillis();
            costs.add(end-start);
            System.out.println((end-start) + "ms");
        }
    
        private static void report(String className) {
            IntSummaryStatistics costStats = costs.stream().collect(Collectors.summarizingInt(Long::intValue));
            System.out.println(className + " Test: cost avg: " + costStats.getAverage() + " min: " + costStats.getMin() + " max: " + costStats.getMax() + " count: " + costStats.getCount());
    
            System.out.println(className + " Test: errorCount: " + errorCount);
        }
    
        private static void reset() {
            costs.clear();
            errorCount = 0;
        }
    }
    
    

    使用:

    
    public class ThreatSqlQuery {
    
        public static void main(String[]args) {
    
            PreparedStatementWrapper preparedStatementWrapper = new PreparedStatementWrapper();
    
            try {
    
                String sql = "select p.pid, p.pname, f.fname, f.path from process_event as p inner join file as f on p.fname = f.fname inner join hash as h on f.hash = h.hash where p.eventId = 'Docker-Detect-Event-1626236000' and f.eventId = 'Docker-Detect-Event-1626236000'";
    
                PreparedStatement st = preparedStatementWrapper.create(sql);
    
                PerformanceTestFramework.test(
                        new ConsumerWrapper<>(sqlstr -> {
                            try {
                                preparedStatementWrapper.execute(st, new ArrayList<>());
                            } catch (SQLException ex) {
                                ex.printStackTrace();
                            }
                        }, sql), PerformanceTestFramework.SEQUENTIAL
                );
    
                st.close();
    
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    注意,这里直接使用 Statement 或 PreparedStatement 并发访问可能会有点问题,需要包装成线程安全的。

    
    public class PreparedStatementWrapper {
    
        private Map<Integer, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>();
    
        private static CalciteConnection calciteConnection;
    
        static {
    
            try {
                Class.forName("org.apache.calcite.jdbc.Driver");
    
                Properties info = new Properties();
                info.setProperty("lex", "JAVA");
    
                Connection connection =
                        DriverManager.getConnection("jdbc:calcite:model=/Users/qinshu/workspace/vtdemo/src/main/resources/vt.json", info);
    
                calciteConnection =
                        connection.unwrap(CalciteConnection.class);
    
            } catch (Exception e1) {
                //
            }
        }
    
        public PreparedStatement create(String sql) throws SQLException {
            int hash = sql.hashCode();
            if (preparedStatementMap.get(hash) != null) {
                return preparedStatementMap.get(hash);
            }
            synchronized (sql) {
                preparedStatementMap.put(hash, calciteConnection.prepareStatement(sql));
                return preparedStatementMap.get(hash);
            }
        }
    
        public void execute(PreparedStatement st, List<PrepareStatementParamObject> params) throws SQLException {
            synchronized (st) {
                if (params != null) {
                    for (PrepareStatementParamObject paramObject: params) {
                        st.setObject(paramObject.getIndex(), paramObject.getValue());
                    }
                }
                ResultSet result = st.executeQuery();
                logResult(result);
                result.close();
            }
        }
    
        private static void logResult(ResultSet resultSet) {
            try {
                ResultSetMetaData metaData = resultSet.getMetaData();
                int colCount = metaData.getColumnCount();
                while(resultSet.next()) {
                    for (int i=1; i <= colCount; i++) {
                        System.out.printf(resultSet.getString(i) + " ");
                    }
                    System.out.println();
                }
            } catch (Exception ex) {
                //
            }
        }
    }
    
    

    但是,PreparedStatement 使用 ConcurrentHashMap 是不合适的。因为 PreparedStatement 是线程不安全的,也不推荐在多线程里复用。更合适的方法是,采用对象池。

    PrepareStatementFactory.java

    
    /**
     * PrepareStatement对象工厂,和PrepareStatementPool是一对
     */
    public class PrepareStatementFactory extends BaseKeyedPooledObjectFactory<String, PreparedStatement> {
    
        public final PreparedStatementWrapper preparedStatementWrapper;
    
        public PrepareStatementFactory(PreparedStatementWrapper preparedStatementWrapper) {
            this.preparedStatementWrapper = preparedStatementWrapper;
        }
    
        /**
         * 创建对象
         */
        @Override
        public PreparedStatement create(String sql) throws Exception {
            return preparedStatementWrapper.create(sql);
        }
    
        @Override
        public PooledObject<PreparedStatement> wrap(PreparedStatement preparedStatement) {
            return new DefaultPooledObject<>(preparedStatement);
        }
    
        /**
         * 验证对象是否有效
         */
        @Override
        public void activateObject(String sql, PooledObject<PreparedStatement> p) {
        }
    
        @Override
        public void passivateObject(String sql, PooledObject<PreparedStatement> p) {
        }
    
    }
    
    

    PrepareStatementPool.java

    
    /**
     * PrepareStatement对象池
     */
    public class PrepareStatementPool {
    
        private final PreparedStatementWrapper preparedStatementWrapper;
        private GenericKeyedObjectPool<String, PreparedStatement> objectPool;
    
        /**
         * 对象池每个key最大实例化对象数
         */
        private final static int TOTAL_PERKEY = 30;
        /**
         * 对象池每个key最大的闲置对象数
         */
        private final static int IDLE_PERKEY = 10;
    
        public PrepareStatementPool(PreparedStatementWrapper preparedStatementWrapper) {
            this.preparedStatementWrapper = preparedStatementWrapper;
    
            // 初始化对象池
            initObjectPool();
        }
    
        public PreparedStatementWrapper getPreparedStatementWrapper() {
            return preparedStatementWrapper;
        }
    
        /**
         * 初始化对象池
         */
        public void initObjectPool() {
            GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
            config.setMaxTotalPerKey(TOTAL_PERKEY);
            config.setMaxIdlePerKey(IDLE_PERKEY);
            config.setBlockWhenExhausted(true);
            KeyedPooledObjectFactory objectFactory = new PrepareStatementFactory(preparedStatementWrapper);
            if (objectPool == null) {
                objectPool = new GenericKeyedObjectPool<>(objectFactory, config);
            }
        }
    
        /**
         * 从对象池中获取对象
         */
        public PreparedStatement borrow(String sql) {
            try {
                return objectPool.borrowObject(sql);
            } catch (Exception ex) {
                throw new RuntimeException(ex.getCause());
            }
        }
    
        /**
         * 归还对象
         */
        public void returnObject(String sql, PreparedStatement p) {
            try {
                objectPool.returnObject(sql, p);
            } catch (Exception ex) {
                throw new RuntimeException(ex.getCause());
            }
        }
    
    }
    
    

    PreparedStatementWrapper.java

    
    /**
     * 预编译语句缓存
     * NOTE: 应用方,需要作为@Component注入spring容器中
     */
    public class PreparedStatementWrapper {
    
        private static final Logger logger = LoggerFactory.getLogger(PreparedStatementWrapper.class);
        private static final String LEX = "lex";
        private static final String CALCITE_DRIVER = "org.apache.calcite.jdbc.Driver";
        private static final String DEFAULT_URL = "jdbc:calcite:model=src/main/resources/virtualtable.json";
        private static final String ID = "id";
    
        /**
         * 创建连接
         */
        public CalciteConnection connection() {
            try {
                Class.forName(CALCITE_DRIVER);
                Properties info = new Properties();
                info.setProperty(LEX, Lex.JAVA.name()); //java类型,还有MYSQL、SQL_SERVER等类型
                Connection connection = DriverManager.getConnection(DEFAULT_URL, info);
                return connection.unwrap(CalciteConnection.class);
            } catch (Exception ex) {
                logger.error("PreparedStatementWrapper.connection error");
                throw new RuntimeException(ex);
            }
        }
    
        /**
         * 创建PreparedStatement对象
         */
        public PreparedStatement create(String sql) throws SQLException {
            return connection().prepareStatement(sql);
        }
    
        /**
         * 执行查询
         *
         * @param params 查询参数
         */
        public ResultSet execute(PreparedStatement preparedStatement, List<PrepareStatementParam> params) throws SQLException {
            if (params != null && !params.isEmpty()) {
                for (PrepareStatementParam paramObject : params) {
                    // 赋值查询参数
                    preparedStatement.setObject(paramObject.getIndex(), paramObject.getValue());
                }
            }
            // 执行查询
            ResultSet result = preparedStatement.executeQuery();
            preparedStatement.clearParameters();
            return result;
        }
    }
    
    

    使用:

    
    public List<TableData> query(String sql, List<PrepareStatementParam> params) {
        // 1、从对象池中获取对象
        PreparedStatement preparedStatement = prepareStatementPool.borrow(sql);
        try {
            // 2、执行SQL
            ResultSet resultSet = preparedStatementWrapper.execute(preparedStatement, params);
            return preparedStatementWrapper.getResult(resultSet);
        } catch (SQLException ex) {
            logger.error("CalciteTemplate.query error,sql:{}", sql);
            throw new RuntimeException(ex);
        } finally {
            // 3、释放对象
            prepareStatementPool.returnObject(sql, preparedStatement);
        }
    }
    
    

    并发编程小提示:

    • 当要并发地存储和访问无实例无状态单例的时候,适合使用 ConcurrentHashMap;
    • 当要并发存储的是有实例有状态的对象,且不适合在多线程中公共访问时,适合采用对象池。

  • 相关阅读:
    Atitit nosql的艺术 attilax著作 目录 1. 1.5NoSQL数据库的类型 1 1.1. 1.5.1键值(Key/Value)存储 1 1.2. 1.5.2面向文档的数据库 1 1
    Atitit 常见信息化系统类别erp mes crm cms oa 目录 1.  企业资源规划(ERP)、客户关系管理(CRM)、协同管理系统(CMS)是企业信息化的三大代表之作 1 2. 概
    Atitit 信息管理概论 艾提拉总结 信息的采集 信息格式转换 信息整合 信息的tag标注 信息的结构化 信息检索,,索引 压缩 信息分析 汇总 第1章 信息管理的基本概念 第
    Atitit 产品化法通则 目录 1. 何谓软件产品化? 1 2. 产品化优点 vs 项目化 2 2.1. 软件复用率提高 2 2.2. ,项目化交付 2 2.3. 维护成本高 2 3. 产品金字塔
    Atitit 人工智能 统计学 机器学习的相似性 一些文摘收集 没有人工智能这门功课,人工智能的本质是统计学和数学,就是通过机器对数据的识别、计算、归纳和学习,然后做出下一步判断和决策的科学
    Atitit mybatis spring整合。读取spring、yml、文件的mysql url 步骤,读取yml,文件,使用ongl定位到url pwd usr 读取mybatis模板配置,
    关于一个大型web系统构架图的理解
    关于《王福朋petshop4.0视频教程》下载的更新
    不完全接触Node.js
    毕业设计那些事
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/14998187.html
Copyright © 2020-2023  润新知