有时候要测试一下某个功能的并发能力,又不要想借助于其他测试工具,索性就自己写简单的demo模拟一个并发请求就最方便了。如果熟悉jemter的测试某接口的并发能力其实更专业,此处只是自己折腾着玩。
CountDownLatch和CyclicBarrier是jdk concurrent包下非常有用的两个并发工具类,它们提供了一种控制并发流程的手段。其实查看源码它们都是在内部维护了一个计数器控制流程的
CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。
CountDownLatch和CyclicBarrier的区别
CountDownLatch的计数器,线程完成一个记录一个,计数器是递减 计数器,只能使用一次
CyclicBarrier的计数器 更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行,计数器是递增 计数器提供reset功能,可以多次使用
另外Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
通常我们模拟并发请求,一般都是多开几个线程,发起请求就好了。但是方式,一般会存在启动的先后顺序了,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?是本文想说的点,java中提供了闭锁 CountDownLatch, CyclicBarrier 刚好就用来做这种事就最合适了。
下面分别使用CountDownLatch和CyclicBarrier来模拟并发的请求
CountDownLatch模拟
package com.test;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.concurrent.CountDownLatch;publicclass LatchTest {
publicstaticvoidmain(String[] args)throws InterruptedException {
Runnable taskTemp =new Runnable() {
// 注意,此处是非线程安全的,留坑privateint iCounter;
@Override
publicvoid run() {
for(inti = 0; i < 10; i++) {
// 发起请求// HttpClientOp.doGet("https://www.baidu.com/");iCounter++;
System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
LatchTest latchTest =new LatchTest();
latchTest.startTaskAllInOnce(5, taskTemp);
}
publiclongstartTaskAllInOnce(intthreadNums,finalRunnable task)throws InterruptedException {
finalCountDownLatch startGate =newCountDownLatch(1);
finalCountDownLatch endGate =new CountDownLatch(threadNums);
for(inti = 0; i < threadNums; i++) {
Thread t =new Thread() {
publicvoid run() {
try {
// 使线程在此等待,当开始门打开时,一起涌入门中 startGate.await();
try {
task.run();
} finally {
// 将结束门减1,减到0时,就可以开启结束门了 endGate.countDown();
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
};
t.start();
}
longstartTime = System.nanoTime();
System.out.println(startTime + " [" + Thread.currentThread() + "] All thread is ready, concurrent going...");
// 因开启门只需一个开关,所以立马就开启开始门 startGate.countDown();
// 等等结束门开启 endGate.await();
longendTime = System.nanoTime();
System.out.println(endTime + " [" + Thread.currentThread() + "] All thread is completed.");
returnendTime - startTime;
}
}
执行结果
CyclicBarrier模拟
// 与 闭锁 结构一致publicclass LatchTest {
publicstaticvoidmain(String[] args)throws InterruptedException {
Runnable taskTemp =new Runnable() {
privateint iCounter;
@Override
publicvoid run() {
// 发起请求// HttpClientOp.doGet("https://www.baidu.com/");iCounter++;
System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter);
}
};
LatchTest latchTest =new LatchTest();// latchTest.startTaskAllInOnce(5, taskTemp);latchTest.startNThreadsByBarrier(5, taskTemp);
}
publicvoidstartNThreadsByBarrier(intthreadNums, Runnable finishTask)throws InterruptedException {
// 设置栅栏解除时的动作,比如初始化某些值CyclicBarrier barrier =new CyclicBarrier(threadNums, finishTask);
// 启动 n 个线程,与栅栏阀值一致,即当线程准备数达到要求时,栅栏刚好开启,从而达到统一控制效果for(inti = 0; i < threadNums; i++) {
Thread.sleep(100);
newThread(new CounterTask(barrier)).start();
}
System.out.println(Thread.currentThread().getName() + " out over...");
}
}classCounterTaskimplements Runnable {
// 传入栅栏,一般考虑更优雅方式private CyclicBarrier barrier;
publicCounterTask(final CyclicBarrier barrier) {
this.barrier = barrier;
}
publicvoid run() {
System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " is ready...");
try {
// 设置栅栏,使在此等待,到达位置的线程达到要求即可开启大门 barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " started...");
}
}
执行结果
并发请求操作流程示意图如下:
此处设置了一道门,以保证所有线程可以同时生效。但是,此处的同时启动,也只是语言层面的东西,也并非绝对的同时并发。具体的调用还要依赖于CPU个数,线程数及操作系统的线程调度功能等,不过咱们也无需纠结于这些了,重点在于理解原理!
毕竟测试并发 还得用专业的工具 jmeter 还是很方便的. 欢迎工作一到五年的Java工程师朋友们加入Java群: 891219277
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!