Java 多线程压力测试类

代码由来

测试程序中除了单次可行性测试以外,一般都会有多线程的压力测试,这个工具类就是为了解决对某个方法的多线程压力测试而准备的。
这个工具类的优点是将所有代码封装进了一个类里,通过构造方法传入测试对象以及测试目标之后,直接调用start()方法即可完成调用,使用起来非常的简单
最最关键的是这个测试工具类的信息输出也非常的完整,任务总耗时,单笔任务平均耗时,单笔任务最大耗时,还有tps都有输出,更关键的是大数量的任务可以分段打印统计结果,还有就是贴心的输出了每条线程最终执行的任务数量。

实现过程

优秀的代码都来自于好的设计,写代码最怕的就是没想法。
原来的这个类是一个封闭性特别不好的类,但也实现了大部分的信息统计的需求,但当我这两天重新审视原来的代码,想怎样把它抽取成一个工具类的时候,我感觉我开始了一次点石成金的旅程。
从开始的减少自定义类的使用,到减少调用println,再到Runnable的构造以及countAndOutput()的抽取,到增加线程任务数量的统计,到最后调试计数规则避免造成bug。完美的走过了一条从修改边幅到重构结构再到增加功能最后完善细节的编程之路。
通过这个过程,让我体会到代码设计的重要性以及功能实现后代码重构的必要性。

如果这篇博客帮助到了你,你可给它点个赞,这将是对我莫大的鼓励,谢谢!

代码

新版本


import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zhangyipeng
 * @date 2020-12-16
 */
public class MultithreadTest {

    /**
     * 多线程并发调度测试
     *
     * @param task        任务
     * @param executeNum  执行次数
     * @param threadNum   线程数量
     * @param intervalNum 输出间隔
     */
    public static void test(Runnable task, Integer executeNum, Integer threadNum, Integer intervalNum) {
        //数据传递容器,用于接收多线程数据
        LinkedBlockingQueue<Long> records = new LinkedBlockingQueue<>();
        //多线程任务执行次数统计
        AtomicInteger executeCounter = new AtomicInteger();
        //使用CyclicBarrier确保所有线程同时开始执行任务
        CyclicBarrier cyclicBarrier = new CyclicBarrier(threadNum);
        //开始执行
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                //开始对齐
                await(cyclicBarrier);
                //在统计到的执行完毕次数达到目标次数之前,一直放行线程执行
                while (executeCounter.getAndIncrement() < executeNum) {
                    records.offer(timekeeper(task));
                }
            }).start();
        }
        // 输出测试结果
        ArrayList<Long> totalRecords = new ArrayList<>(executeNum);
        ArrayList<Long> intervalRecords = new ArrayList<>(intervalNum);
        for (int j = 1; totalRecords.size() < executeNum; j++) {
            for (int i = 0; i < intervalNum; i++) {
                intervalRecords.add(take(records));
            }
            outPut("interval output " + j, intervalRecords);
            totalRecords.addAll(intervalRecords);
            intervalRecords.clear();
        }
        outPut("finally output", totalRecords);
    }

    /**
     * 输出数据集合的总和、最大值、平均值、tps
     *
     * @param msg  提示语
     * @param data 数据集合
     */
    public static void outPut(String msg, Collection<Long> data) {
        Long sum = data.stream().reduce(0L, Long::sum);
        Long max = data.stream().reduce(0L, Long::max);
        double avg = (double) sum / data.size();
        double tps = (double) data.size() * 1000 / sum;
        System.out.printf("==============================\n"
                        + "%s:\n"
                        + "total elapse is %d ms\n"
                        + "max   elapse is %d ms\n"
                        + "avg   elapse is %.2f ms\n"
                        + "the    tps   is %.4f\n",
                msg, sum, max, avg, tps);
    }

    /**
     * cyclicBarrier.await 的委托方法
     *
     * @param cyclicBarrier 委托对象
     */
    private static void await(CyclicBarrier cyclicBarrier) {
        try {
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    /**
     * BlockingQueue.take 的委托方法
     *
     * @param blockingQueue 委托对象
     * @param <T>           泛型
     * @return 结果
     */
    private static <T> T take(BlockingQueue<T> blockingQueue) {
        try {
            return blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 任务执行时间统计
     *
     * @param task 任务对象
     * @return 执行时长
     */
    private static long timekeeper(Runnable task) {
        long start = System.currentTimeMillis();
        task.run();
        return System.currentTimeMillis() - start;
    }

}

老版本

仅为留念,请不要使用。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 多线程测试类
 * 
 * @author zhangyp
 * @creatTime 2020-04-05 02:06
 *
 */
public class MultithreadTest {

	private Method method;
	private Object instance;
	private Object[] params;

	/** 所有线程要执行的总次数 */
	private final int executTimes;

	/** 线程数 */
	private final int threadNums;

	/** 输出间隔任务数 */
	private final int intervalNums;

	/** 多线程执行计数器 */
	private final AtomicInteger counter = new AtomicInteger(0);
	
	/** 多线程任务总耗时计数器 */
	private final AtomicLong totalElapse = new AtomicLong(0);

	/** 多线程单笔任务最长时间计数器 */
	private final AtomicLong maxElapse = new AtomicLong(0);

	/** 多线程最近一次输出间隔总耗时计数器 */
	private final AtomicLong intervalTotalElapse = new AtomicLong(0);

	/** 多线程最近一次输出间隔里的单笔任务最长时间计数器 */
	private final intervalMaxElapse = new AtomicLong(0);
	
	/** 每条线程执行任务数统计 */
	private final Map<String, Integer> threadTaskCounter = new TreeMap<String, Integer>();
	
	/**
	 * 接收执行参数
	 * 
	 * @param instance		实例对象
	 * @param method		方法对象
	 * @param params		方法参数
	 * @param threadNums	线程数量
	 * @param executTimes	执行次数
	 * @param intervalNums	间隔次数
	 */
	public MultithreadTest(Method method, Object instance, Object[] params, int threadNums, int executTimes, int intervalNums) {
		super();
		this.method = method;
		this.params = params;
		this.instance = instance;
		this.threadNums = threadNums;
		this.executTimes = executTimes;
		this.intervalNums = intervalNums;
	}

	/**
	 * 多线程执行
	 */
	public void start() {
		//使用CyclicBarrier确保所有线程同时开始执行任务
		CyclicBarrier cyclicBarrier = new CyclicBarrier(threadNums);
		// 构造任务
		Runnable runnable = new Runnable() {
			public void run() {
				//开始对齐
				try {
					cyclicBarrier.await();
				} catch (InterruptedException | BrokenBarrierException e) {
					e.printStackTrace();
				}
				//在统计到的执行完毕次数达到目标次数之前,一直放行线程执行
				while (counter.get() < executTimes) {
					long start = System.currentTimeMillis();
					invoke();
					countAndOutput(System.currentTimeMillis() - start);
				}
			}
		};
		//开始执行
		for (int i = 0; i < threadNums; i++) {
			new Thread(runnable).start();
		}
	}

	/**
	 * 计数与输出
	 * @param elapse	单次任务耗时
	 */
	private synchronized void countAndOutput(long elapse) {
		int thisTimeNumber = counter.addAndGet(1);
		//达到目标次数后不再统计后续结果
		if (thisTimeNumber > executTimes) {
			return;
		}
		//记录本线程执行任务数
		//Java6的写法
		Integer threadTasks = threadTaskCounter.get(Thread.currentThread().getName());
		threadTasks = threadTasks == null ? 1 : threadTasks + 1;
		threadTaskCounter.put(Thread.currentThread().getName(), threadTasks);
		//Java8可以一行搞定
//		threadTaskCounter.put(Thread.currentThread().getName(), threadTaskCounter.getOrDefault(Thread.currentThread().getName(), 0) + 1);
		// 记录执行时间
		intervalTotalElapse.addAndGet(elapse);
		totalElapse.addAndGet(elapse);
		// 记录最长单笔任务时间
		if (elapse > intervalMaxElapse.get()) {
			intervalMaxElapse.set(elapse);
			if (elapse > maxElapse.get()) {
				maxElapse.set(elapse);
			}
		}
		// 单次间隔结束输出结果
		if (intervalNums != 0 && (thisTimeNumber % intervalNums == 0 || thisTimeNumber == executTimes)) {
			intervalOutput();
			// 清空单次间隔记录的结果
			intervalMaxElapse.set(0L);
			intervalTotalElapse.set(0L);
		}
		//全部任务执行完成输出结果
		if (thisTimeNumber == executTimes) {
			lastOutput();
		}
	}

	/** 间隔输出结果 */
	private void intervalOutput() {
		System.out.printf("\ninterval elapse is %d ms"
						+ "\n  max    elapse is %d ms"
						+ "\n  avg    elapse is %.2f ms"
						+ "\ninterval  tps   is %.4f\n",
						intervalTotalElapse.get(), 
						intervalMaxElapse.get(),
						(float) intervalTotalElapse.get() / intervalNums,
						(float) intervalNums * 1000 / intervalTotalElapse.get());
	}

	/** 最后输出结果 */
	private void lastOutput() {
		System.out.printf("\nThe total elapse is %d ms"
						+ "\nThe max   elapse is %d ms"
						+ "\nThe avg   elapse is %.2f ms"
						+ "\nThe total  tps   is %.4f\n", 
						totalElapse.get(), 
						maxElapse.get(), 
						(float) totalElapse.get() / executTimes, 
						(float) executTimes * 1000 / totalElapse.get());
		System.out.println();
		//输出每条线程执行的任务数
		for (Entry<String, Integer> counter : threadTaskCounter.entrySet()) {
			System.out.printf("The %s executed %2d times\n", counter.getKey(), counter.getValue());
		}
	}
	
	/** 执行方法 */
	private void invoke() {
		try {
			method.invoke(instance, params);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.getTargetException().printStackTrace();
		}
	}

}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值