java中如何模拟真正的同时并发请求?
模拟并发的方式 1.PostmanPostman是一个http模拟请求的客户端
1.编写一个简单接口处理请求@RestController @RequestMapping("/test") public class TestConrtoller { @GetMapping("/get") public String testDemo() { return "data"; } }
2. 在postman中为了便于 *** 作,一般会将http://127.0.0.1:8080 是经常使用的地址+端口号,可以设置为开发环境
选择global
输入信息
以后再进行测试就能这样搞简写了
3. 如何模拟并发测试添加一个目录用于保存请求
把刚才测试的demo的例子放进这个concurrent目录下
在concurrent下看到这个接口测试
选择并发测试:
这个时候d出我们想要的框了
点击Run Concurrency
- 你可以立马感觉到CPU在 “燃烧” ,因为要记录并 打印日志 ,显示的话是一条一条来的,其实测试的速度,要比你看到的打印的日志的速度快, 绿色表示正常
JMeter也是一款Java编写的性能测试工具
- http://jmeter.apache.org/下载地址: http://jmeter.apache.org/需要JDK1.8+ 的的环境才能运行需要 JDK1.8+ 的的环境才能运行解压到你觉得合适的目录下(注意最好是英文路径)进入它的 bin目录下 启动 jmeter.bat 即可
使用很简单,首先在 测试计划部分 右键新建一个 线程组
设置好并发参数后右键添加 HTTP请求 (基本信息设置好没有OK哈,直接添加HTTP请求)
填写HTTP请求参数
右键http请求 添加 监听器 ,这里选择是图形结果
右键http请求 再添加一个查看 结果树 吧
在运行之前在 选项栏Options 打开 log Viewer
点击绿色箭头开始运行
执行成功,来感受一下结果:
查看 结果树
3.Java代码模拟 CoundDownLatch模拟并发请求java中模拟并发请求,自然是很方便的,只要多开几个线程,发起请求就好了。但是,这种请求,一般会 存在启动的先后顺序了 ,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?java中提供了 闭锁 CountDownLatch、信号量Semaphore 、 同步屏障CyclicBarrier, 刚好就用来做这种事就最合适了。
只需要:1. 开启n个线程,加一个闭锁,开启所有线程; 2. 待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。
CountDownLatch俗称: (同步计数器/闭锁) ,可以使 一个线程等待其他线程全部执行完毕后再执行。 类似join()的效果。
- 场景 :主要用来解决 一个线程等待 N 个线程的场景 。 通常用来汇总各个线程执行后的结果
- CountDownLatch内部通过一个 计数器 来 控制等待线程数 ,该计数器的 *** 作是 原子 *** 作 ,即同时只能有一个线程去更新该计数器 。调用 await()方法的线程会一直处于 阻塞状态 ,直到其他线程调用 countDown() 使当前计数器的值变为 0 ,每次调用 countDown() 方法计数器的值 减1 。当计数器值 减至0时, 所有 因调用await()方法而处于 等待状态 的线程就会被 唤 醒然后 继续往下执行 。这种现象只会出现一次,因为计数器不能被重置 。
下图和它的方法可以体现出来:
CountDownLatch类只提供了 一个构造器 :
public CountDownLatch(int count) { }; //参数count为计数值
下面这3个方法是CountDownLatch类中最重要的方法(
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行 public void await() throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //将count值减1 public void countDown() { };
并发请求 *** 作流程示意图如下:
- 设置了一道门,以保证所有线程可以同时生效。但是,这里的同时启动,也只是 语言层面 的东西,也 并非绝对的同时并发 。具体的调用还要 依赖于CPU个数,线程数及 *** 作系统的线程调度功能等 ,不过咱们也无需纠结于这些了,重点在于理解原理!与 CountDownLatch 有类似功能的,还有工具栅栏 CyclicBarrier, 也是提供一个 等待所有线程到达某一点后, 再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是 最后那一个开关,所以侧重点是不一样的。
@Slf4j public class LatchTest1 { public static void main(String[] args) throws InterruptedException { //开启并发模式 LatchTest1 latchTest = new LatchTest1(); latchTest.startTaskAllInonce(10000); } public long startTaskAllInonce(int threadNums) throws InterruptedException { // 定义线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //开始门 final CountDownLatch startGate = new CountDownLatch(1); //结束门 final CountDownLatch endGate = new CountDownLatch(threadNums); for (int i = 0; i < threadNums; i++) { executorService.execute(() -> { try { // 使线程在此等待,当开始门打开时,一起涌入门中 startGate.await(); try { // 发起请求 request(); } finally { // 将结束门减1,减到0时,就可以开启结束门了 endGate.countDown(); } } catch (InterruptedException ie) { ie.printStackTrace(); } }); } long startTime = System.nanoTime(); log.info(startTime + " [" + Thread.currentThread() + "] 所有线程都准备好了,准备并发运行..."); //因开始门只需一个开关,所以立马就开启开始门 startGate.countDown(); //等结束门开启 endGate.await(); //返回并发执行耗时(纳秒) long endTime = System.nanoTime(); log.info(endTime + " [" + Thread.currentThread() + "] 所有的线程执行完成."); return endTime - startTime; } private static void request() { String url = "http://localhost:8080/test/get"; try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpGet httpGet = new HttpGet(url); String responseBody = httpClient.execute(httpGet, httpResponse -> { int status = httpResponse.getStatusLine().getStatusCode(); if (status < 200 || status >= 300) { throw new RuntimeException("请求异常"); } HttpEntity entity = httpResponse.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; }); log.info("请求成功>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>{}", responseBody); } catch (IOException e) { e.printStackTrace(); } } }CyclicBarrier模拟并发请求
CyclicBarrier俗称: 同步屏障 ,可以使 一组线程互相等待,“直到所有线程到达某个公共的执行点后在继续执行” 。
- 场景 :主要 用于 N 个线程之间互相等待 。 就像几个驴友约好爬山,要等待所有驴友都到齐后才能统一出发。
- 声明该对象时需要初始化等待线程数 ,调用 await() 方法会使得线程阻塞,直到 指定数量的线程都调用await方法时,所有被阻塞的线程会被唤醒,继续执行 。
与CountDownLatch的区别是:CountDownLatch是 一组线程等待另外一组线程执行完在执行,而CyclicBarrier是 一组线程之间相互等待,直到所有线程执行到某个点在执行 。
public long startTaskAllInonce(int threadNums) throws InterruptedException { // 定义线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //声明同步屏障 final CyclicBarrier startGate = new CyclicBarrier (threadNums); //结束门 final CountDownLatch endGate = new CountDownLatch(threadNums); for (int i = 0; i < threadNums; i++) { executorService.execute(()-> { try { // 使线程在此等待,当所有线程到达这个位置时,一起涌入门中 startGate.await(); try { //模拟请求 request(); } finally { // 将结束门减1,减到0时,就可以开启结束门了 endGate.countDown(); } } catch (InterruptedException | BrokenBarrierException ie) { ie.printStackTrace(); } }); } long startTime = System.nanoTime(); log.info(startTime + " [" + Thread.currentThread() + "] 所有线程都准备好了,准备并发运行..."); //等结束门开启 endGate.await(); //返回并发执行耗时(纳秒) long endTime = System.nanoTime(); log.info(endTime + " [" + Thread.currentThread() + "] 所有的线程执行完成."); //关闭线程池 executorService.shutdown(); return endTime - startTime; }CountDownLatch+Semaphore模拟并发限流 需要用到的另一个类Semaphore
称之为 信号量 ,与 互斥锁ReentrantLock用法类似 ,区别就是Semaphore 共享的资源是多个,允许多个线程同时竞争成功。
- 场景 : 限流场景使用 ,限定 最多允许N个线程可以访问某些资源 。 就像车辆行驶到路口,必须要看红绿灯指示,要等到绿灯才能通行。
- 与CountDownLatch区别 : Semaphore的“计数“被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它更多地用来限流,类似 阀门 的 功能。 如果限定某些资源 最多有N个线程可以访问 ,那么 超过 N个则 不允许再有线程来访问 ,同时当现有 线程结束后 ,就会 释放 ,然后 允许新的线程进来 。 有点类似于锁的lock与 unlock过程。相对来说他也有2个主要的方法:用于获取权限的 acquire() ,其底层实现与CountDownLatch. countdown() 类似;用于释放权限的 release() ,其底层实现与acquire()是一个 互逆 的过程。
public long startTaskAllInonce(int threadNums,int threadTotal) throws InterruptedException { // 定义线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //声明同步屏障 final CyclicBarrier startGate = new CyclicBarrier (threadNums); //结束门 final CountDownLatch endGate = new CountDownLatch(threadNums); // 定义信号量 最大的线程数量(同一时间每次最大并发流量) final Semaphore semaphore = new Semaphore(threadTotal); for (int i = 0; i < threadNums; i++) { executorService.execute(()->{ try { // 使线程在此等待,当所有线程到达这个位置时,一起涌入门中 startGate.await(); try { //获取权限等到了200就会被唤醒所有线程 semaphore.acquire(); //模拟请求 request(); //权限计数 semaphore.release(); } finally { // 将结束门减1,减到0时,就可以开启结束门了 endGate.countDown(); } } catch (InterruptedException | BrokenBarrierException ie) { ie.printStackTrace(); } }); } long startTime = System.nanoTime(); log.info(startTime + " [" + Thread.currentThread() + "] 所有线程都准备好了,准备并发运行..."); //等结束门开启 endGate.await(); //返回并发执行耗时(纳秒) long endTime = System.nanoTime(); log.info(endTime + " [" + Thread.currentThread() + "] 所有的线程执行完成."); return endTime - startTime; }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)