目录
- 代码由来
- 实现过程
- 代码
- 新版本
- 老版本
代码由来
测试程序中除了单次可行性测试以外,一般都会有多线程的压力测试,这个工具类就是为了解决对某个方法的多线程压力测试而准备的。
这个工具类的优点是将所有代码封装进了一个类里,通过构造方法传入测试对象以及测试目标之后,直接调用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();
}
}
}