「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

java中如何模拟真正的同时并发请求?

模拟并发的方式

=======

1.Postman

=========

Postman是一个http模拟请求的客户端

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

1.编写一个简单接口处理请求

==============

@RestController

@RequestMapping(“/test”)

public class TestConrtoller {

@GetMapping(“/get”)

public String testDemo() {

return “data”;

}

}

2. 在postman中为了便于操作,一般会将http://127.0.0.1:8080 是经常使用的地址+端口号,可以设置为开发环境

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

选择global

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

输入信息

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

以后再进行测试就能这样搞简写了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

3. 如何模拟并发测试

============

添加一个目录用于保存请求

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

把刚才测试的demo的例子放进这个concurrent目录下

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

在concurrent下看到这个接口测试

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

选择并发测试:

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

这个时候弹出我们想要的框了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

点击Run Concurrency

  • 你可以立马感觉到CPU在 “燃烧” ,因为要记录并 打印日志 ,显示的话是一条一条来的,其实测试的速度,要比你看到的打印的日志的速度快, 绿色表示正常

2.并发模拟工具JMeter

==============

JMeter也是一款Java编写的性能测试工具

  • http://jmeter.apache.org/下载地址: http://jmeter.apache.org/需要JDK1.8+ 的的环境才能运行需要 JDK1.8+ 的的环境才能运行解压到你觉得合适的目录下(注意最好是英文路径)进入它的 bin目录下 启动 jmeter.bat 即可

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

使用很简单,首先在 测试计划部分 右键新建一个 线程组

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

设置好并发参数后右键添加 HTTP请求 (基本信息设置好没有OK哈,直接添加HTTP请求)

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

填写HTTP请求参数

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

右键http请求 添加 监听器 ,这里选择是图形结果

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

右键http请求 再添加一个查看 结果树 吧

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

在运行之前在 选项栏Options 打开 log Viewer

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

点击绿色箭头开始运行

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

执行成功,来感受一下结果:

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

查看 结果树

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

3.Java代码模拟

==========

CoundDownLatch模拟并发请求

====================

java中模拟并发请求,自然是很方便的,只要多开几个线程,发起请求就好了。但是,这种请求,一般会 存在启动的先后顺序了 ,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?java中提供了 闭锁 CountDownLatch、信号量Semaphore 、 同步屏障CyclicBarrier, 刚好就用来做这种事就最合适了。

只需要:

====

  1. 开启n个线程,加一个闭锁,开启所有线程;

  2. 待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。

CountDownLatch俗称: (同步计数器/闭锁) ,可以使 一个线程等待其他线程全部执行完毕后再执行。 类似join()的效果。

  • 场景 :主要用来解决 一个线程等待 N 个线程的场景 。 通常用来汇总各个线程执行后的结果

  • CountDownLatch内部通过一个 计数器 来 控制等待线程数 ,该计数器的操作是 原子操作 ,即同时只能有一个线程去更新该计数器 。调用 await()方法的线程会一直处于 阻塞状态 ,直到其他线程调用 countDown() 使当前计数器的值变为 0 ,每次调用 countDown() 方法计数器的值 减1 。当计数器值 减至0时, 所有 因调用await()方法而处于 等待状态 的线程就会被 唤 醒然后 继续往下执行 。这种现象只会出现一次,因为计数器不能被重置 。

下图和它的方法可以体现出来:

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

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() {

};

并发请求操作流程示意图如下:

「Java多线程」慎用这种方式模拟并发请求,我差点把服务弄蹦了

  • 设置了一道门,以保证所有线程可以同时生效。但是,这里的同时启动,也只是 语言层面 的东西,也 并非绝对的同时并发 。具体的调用还要 依赖于CPU个数,线程数及操作系统的线程调度功能等 ,不过咱们也无需纠结于这些了,重点在于理解原理!与 CountDownLatch 有类似功能的,还有工具栅栏 CyclicBarrier, 也是提供一个 等待所有线程到达某一点后, 再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是 最后那一个开关,所以侧重点是不一样的。

@Slf4j

public class LatchTest1 {

public static void main(String[] args) throws InterruptedException {

//开启并发模式

LatchTest1 latchTest = new LatchTest1();

latchTest.startTaskAllInOnce(10000);

}

/**

  • @param threadNums 并发数

  • @return

  • @throws InterruptedException

*/

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.cu
rrentThread() + “] 所有线程都准备好了,准备并发运行…”);

//因开始门只需一个开关,所以立马就开启开始门

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是 一组线程之间相互等待,直到所有线程执行到某个点在执行 。

/**

  • @param threadNums 并发数

  • @return

  • @throws InterruptedException

*/

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()是一个 互逆 的过程。

/**

  • @param threadNums 总并发数

  • @param threadTotal 同一时间每次最大并发流量

  • @return

  • @throws InterruptedException

*/

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;
}

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值