背景
做实验过程中,想统计某个接口在并发请求的情况下的平均响应时间,如何用Java去实现这个功能呢?
技术实现
一、可能遇到的问题
1、我们可以利用多线程去实现,只要多开几个线程,发起请求就好了,但是这不是真正的并发!因为线程的创建是有先后顺序的,这样做本质还是先创建的线程先执行。
2、操作系统对线程的调度我们是不知道的,我们该如何实现,主线程等待所有子线程执行完毕后,才去统计子线程耗时呢?
这里就要用到Java中的CountDownLatch类。
二、什么是countDownlatch
CountDownLatch是一个同步工具类,它通过一个计数器来实现的,初始值为线程的数量。每当一个线程完成了自己的任务,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已执行完毕,然后在等待的线程就可以恢复执行任务。
关键方法:
countDown(): 每调用一次计数器值-1,直到count被减为0,代表所有线程全部执行完毕。
await(): 等待计数器变为0,即等待所有异步线程执行完毕。
二、利用countDownlatch实现多个线程同时启动
1、实现自定义任务线程
public class MyConcurrentThreadHcode implements Callable<Long> {
private final CountDownLatch latch;
private final CountDownLatch latchTotal;
public MyConcurrentThreadHcode(CountDownLatch latch, CountDownLatch latchTotal) {
this.latch = latch;
this.latchTotal = latchTotal;
}
public Long call() {
System.out.println(Thread.currentThread().getName()+",prepare at: "+System.currentTimeMillis());
try {
//先阻塞在这里,等待主线程将latch的计数器减为0
latch.await();
return doTask();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//执行完一次latchTotal的计数器减一,latchTotal减到0后,主线程才会继续往下执行
latchTotal.countDown();
}
return -1L;
}
public long doTask() {
Map<String, Object> params = new HashMap<String, Object>(7);
params.put("minX", -68.8);
params.put("maxX", -65);
params.put("minY", 17);
params.put("maxY", 20);
params.put("startTimeStr", "2022-01-03T00:00:00");
params.put("endTimeStr", "2022-01-03T10:00:00");
long startTime = System.currentTimeMillis();
String result = HttpUtil.get("http://localhost:6068/test/QueryByHCode", params);
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
}
2、主线程创建多线程,并同时启动
public static void testConcurrentWCodeQuery(int totalThreadNum) throws ExecutionException, InterruptedException {
//01 创建指定threadNum个数的线程
CountDownLatch latchTotal = new CountDownLatch(totalThreadNum);
CountDownLatch latch = new CountDownLatch(1);
List<FutureTask> tasks = new ArrayList<>(totalThreadNum);
for (int i = 0; i < totalThreadNum ; i++) {
MyConcurrentThreadWcode task = new MyConcurrentThreadWcode(latch, latchTotal);
FutureTask futureTask = new FutureTask(task);
tasks.add(futureTask);
Thread thread = new Thread(futureTask);
thread.start();
}
//同时启动多个线程
latch.countDown();
try {
/**所有子线程未执行完,主线程会阻塞在这里*/
latchTotal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long totalTime = 0L;
int num = 0;
for(FutureTask task : tasks)
{
Long time = (Long)task.get();
if(time > 0L){
totalTime += time;
num ++;
}
}
System.out.println("查询平均耗时:" + totalTime/num + "ms");
}
在主线程和任务线程中,创建了两个CountDownLatch变量,一个变量latch的初始计数器为1,变量latchTotal的初始计数器为线程数。
latch变量的作用是在主线程创建完所有子线程后,再启动子线程。
latchTotal变量的作用是在子线程执行完所有任务后,在执行主线程代码。
如此,便能实现多个线程同时启动,并在所有子线程执行完毕之后,统计其总耗时。