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

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

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

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

模拟并发的方式 1.Postman

Postman是一个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在 “燃烧” ,因为要记录并 打印日志 ,显示的话是一条一条来的,其实测试的速度,要比你看到的打印的日志的速度快, 绿色表示正常
2.并发模拟工具JMeter

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5661049.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存