原文链接:https://blog.csdn.net/qq877728715/article/details/121857013
1、并发请求介绍
java中模拟并发请求,只要多开几个线程,发起请求就好了。但是,这种请求,一般会存在启动的先后顺序了,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?java中提供了闭锁 CountDownLatch、信号量Semaphore、同步屏障CyclicBarrier, 刚好就用来做这种事就最合适了
本文主要记录CountDownLatch
1. 开启n个线程,加一个闭锁,开启所有线程;
2. 待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。
2、CountDownLatch执行逻辑
CountDownLatch俗称:(同步计数器/闭锁),可以使一个线程等待其他线程全部执行完毕后再执行。 类似join()的效果。
- 场景:主要用来解决一个线程等待 N 个线程的场景。通常用来汇总各个线程执行后的结果
- CountDownLatch内部通过一个计数器来控制等待线程数,该计数器的操作是原子操作,即同时只能有一个线程去更新该计数器。
- 调用await()方法的线程会一直处于阻塞状态,直到其他线程调用countDown()使当前计数器的值变为 0,每次调用countDown()方法计数器的值减1。
- 当计数器值减至0时,所有因调用await()方法而处于等待状态的线程就会被唤醒然后继续往下执行。
- 这种现象只会出现一次,因为计数器不能被重置。
并发请求流程示意图:

设置了一道门,以保证所有线程可以同时生效。但是,这里的同时启动,也只是语言层面的东西,也并非绝对的同时并发。具体的调用还要依赖于CPU个数,线程数及操作系统的线程调度功能等,不过咱们也无需纠结于这些了,重点在于理解原理!
与 CountDownLatch 有类似功能的,还有工具栅栏 CyclicBarrier, 也是提供一个等待所有线程到达某一点后,再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是 最后那一个开关,所以侧重点是不一样的。
3、实例代码
示例代码:
1、定义了一个开始门,一个结束门,开始门的线程数是1,结束门的线程数是1500,
2、开始门用来阻塞所有的线程,所有线程创建后,在开始门前阻塞;当countDown()==0时,阻塞线程后续的逻辑开始执行
3、结束门用来统计所有线程的执行时间;当开始门开始时开始计时,每执行一个线程countDown,当countDown()==0时,结束门的阻塞失效,程序开始向下执行,线程执行完成,统计执行时间
package com.codesheep.terminaldemo.config;
import com.codesheep.terminaldemo.service.TaskClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName TerminalInfo
* @description:
* @author: codesheep
* @Version 1.0.0
* @createTime: 2022-07-29 13:17:48
*/
@Component
public class TerminalInfo {
private static final Logger logger = LoggerFactory.getLogger(TerminalInfo.class);
private static int TERMINAL_COUNT = 1500;
private static int COUNT = 0;
// 定义线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//开始门
final CountDownLatch startGate = new CountDownLatch(1);
//结束门
final CountDownLatch endGate = new CountDownLatch(TERMINAL_COUNT);
public void process1(){
for (int i = 0; i < TERMINAL_COUNT; i++) {
executorService.execute(() -> {
try {
// 使线程在此等待,当开始门打开时,一起涌入门中
startGate.await();
try {
// 执行业务逻辑:发起请求
process2();
} finally {
// 业务执行完毕:将结束门减1,减到0时,就可以开启结束门了
endGate.countDown();
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
});
}
long startTime = System.nanoTime();
logger.info(startTime + " [" + Thread.currentThread() + "] 所有线程都准备好了,准备并发运行...");
// startGate为0,立马执行该startGate阻塞的线程(线程向后执行),因开始门只需一个开关,所以立马就开启开始门
startGate.countDown();
// 阻塞线程不让向下执行(直到endGate为0,此处是所有线程执行完毕):等结束门开启(endGate为0,所有的任务执行完毕,统计执行时间)
try {
endGate.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回并发执行耗时(纳秒)
long endTime = System.nanoTime();
logger.info(endTime + " [" + Thread.currentThread() + "] 所有的线程执行完成."+" 耗时:"+(endTime - startTime)+" ns");
}
private void process2() {
TaskClient newTaskClient = new TaskClient("1","119.167.1.112", 8888);
newTaskClient.connect();
COUNT++;
logger.info("٩(๑❛❛๑)۶ ===终端数=== " + COUNT +" === "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}