web应用程序并发测试_测试并发应用

web应用程序并发测试

本文是我们名为Java Concurrency Essentials的学院课程的一部分。

在本课程中,您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识,并学习诸如原子性,同步和线程安全性的概念。 在这里查看 !

目录

1.简介 2. SimpleBlockingQueue
2.1。 测试阻止操作 2.2。 测试正确性 2.3。 测试性能
3.测试框架
3.1。 JMock 3.2。 Grobo Utils

1.简介

本文介绍了多线程应用程序的测试。 我们实现一个简单的阻塞队列,并测试其阻塞行为以及在压力测试条件下的行为和性能。 最后,我们阐明了用于多线程类的单元测试的可用框架。

2. SimpleBlockingQueue

在本节中,我们将实现一个非常基本且简单的阻塞Queue 。 这个队列除了保留我们放入其中的元素并在调用get()时将它们还给他们外,什么也没做。 在新元素可用之前, get()应该会阻塞。

很明显, java.util.concurrent包已经提供了这样的功能,并且不需要再次实现它,但是出于演示目的,我们在这里这样做是为了展示如何测试这样的类。

作为队列的数据结构,我们从java.util包中选择一个标准的LinkedList 。 此列表未同步,并且调用其get()方法不会阻塞。 因此,我们必须同步访问列表,并且必须添加阻止功能。 后者可以通过一个简单的while()循环来实现,当队列为空时,该循环调用列表上的wait()方法。 如果队列不为空,则返回第一个元素:

public class SimpleBlockingQueue<T> {private List<T> queue = new LinkedList<T>();public int getSize() {synchronized(queue) {return queue.size();}}public void put(T obj) {synchronized(queue) {queue.add(obj);queue.notify();}}public T get() throws InterruptedException  {while(true) {synchronized(queue) {if(queue.isEmpty()) {queue.wait();} else {return queue.remove(0);}}}}
}

测试阻止操作

尽管此实现非常简单,但是测试所有功能(尤其是阻止功能)并不是那么容易。 当我们只在空队列上调用get()时,当前线程将被阻塞,直到另一个线程将新项插入队列。 这意味着在单元测试中我们至少需要两个不同的线程。 一个线程阻塞时,另一个线程等待特定的时间。 如果在此期间另一个线程不执行其他代码,则可以假定阻止功能正在运行。 检查阻塞线程没有执行任何其他代码的一种方法是,在引发异常或执行get()调用后的行时,添加一些设置的布尔标志:

private static class BlockingThread extends Thread {private SimpleBlockingQueue queue;private boolean wasInterrupted = false;private boolean reachedAfterGet = false;private boolean throwableThrown;public BlockingThread(SimpleBlockingQueue queue) {this.queue = queue;}public void run() {try {try {queue.get();} catch (InterruptedException e) {wasInterrupted = true;}reachedAfterGet = true;} catch (Throwable t) {throwableThrown = true;}}public boolean isWasInterrupted() {return wasInterrupted;}public boolean isReachedAfterGet() {return reachedAfterGet;}public boolean isThrowableThrown() {return throwableThrown;}}

标志wasInterrupted指示阻塞线程是否被中断,标志标志reachedAfterGet显示执行了get之后的行,最后throwableThrown会告诉我们是否throwableThrown了任何类型的Throwable 。 使用这些标志的getter方法,我们现在可以编写一个单元测试,该测试首先创建一个空队列,启动BlockingThread ,等待一段时间,然后将新元素插入队列。

@Testpublic void testPutOnEmptyQueueBlocks() throws InterruptedException {final SimpleBlockingQueue queue = new SimpleBlockingQueue();BlockingThread blockingThread = new BlockingThread(queue);blockingThread.start();Thread.sleep(5000);assertThat(blockingThread.isReachedAfterGet(), is(false));assertThat(blockingThread.isWasInterrupted(), is(false));assertThat(blockingThread.isThrowableThrown(), is(false));queue.put(new Object());Thread.sleep(1000);assertThat(blockingThread.isReachedAfterGet(), is(true));assertThat(blockingThread.isWasInterrupted(), is(false));assertThat(blockingThread.isThrowableThrown(), is(false));blockingThread.join();}

在插入之前,所有标志都应为false。 如果是这样的话,我们把一个新的元素到队列中,检查标志reachedAfterGet设置为true。 所有其他标志仍应为false。 最后,我们可以join() blockingThread

测试正确性

先前的测试仅显示了如何测试阻塞操作。 更有趣的是真正的多线程测试用例,其中我们有多个线程在队列中添加元素,还有一堆工作线程从队列中检索这些值。 基本上,这意味着创建一些将新元素插入队列的生产者线程,并设置一堆调用get()的工作线程。

但是,我们如何知道工作线程从队列中获得了与生产者线程之前插入的元素完全相同的元素呢? 一种可能的解决方案是让第二个队列在其中基于某些唯一ID(例如UUID)添加和删除元素。 但是,由于我们处于多线程环境中,因此我们还必须同步对第二个队列的访问,并且某些唯一ID的创建也将强制进行某种同步。

更好的解决方案是一些无需任何额外同步即可实现的数学方法。 最简单的方法是使用整数值作为队列元素,这些值是从线程本地随机生成器中检索的。 特殊类ThreadLocalRandom为每个线程管理一个随机生成器; 因此,我们没有任何同步开销。 生产者线程计算由它们插入的元素的总和,而工作线程也计算其本地和。 最后,将所有生产者线程的总和与所有消费者线程的总和进行比较。 如果相同,则可以假定我们很有可能已检索到之前插入的所有任务。

以下单元测试通过将使用者线程和生产者线程作为任务提交到固定线程池来实现这些想法:

@Testpublic void testParallelInsertionAndConsumption() throws InterruptedException, ExecutionException {final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS);final CountDownLatch latch = new CountDownLatch(NUM_THREADS);List<Future<Integer>> futuresPut = new ArrayList<Future<Integer>>();for (int i = 0; i < 3; i++) {Future<Integer> submit = threadPool.submit(new Callable<Integer>() {public Integer call() {int sum = 0;for (int i = 0; i < 1000; i++) {int nextInt = ThreadLocalRandom.current().nextInt(100);queue.put(nextInt);sum += nextInt;}latch.countDown();return sum;}});futuresPut.add(submit);}List<Future<Integer>> futuresGet = new ArrayList<Future<Integer>>();for (int i = 0; i < 3; i++) {Future<Integer> submit = threadPool.submit(new Callable<Integer>() {public Integer call() {int count = 0;try {for (int i = 0; i < 1000; i++) {Integer got = queue.get();count += got;}} catch (InterruptedException e) {}latch.countDown();return count;}});futuresGet.add(submit);}latch.await();int sumPut = 0;for (Future<Integer> future : futuresPut) {sumPut += future.get();}int sumGet = 0;for (Future<Integer> future : futuresGet) {sumGet += future.get();}assertThat(sumPut, is(sumGet));}

我们使用CountDownLatch来等待所有线程完成。 最后,我们可以计算所有提交和检索的整数的总和,并断言两者相等。

很难预测执行不同线程的顺序。 它取决于许多动态因素,例如操作系统处理的中断以及调度程序如何选择下一个要执行的线程。 为了实现更多的上下文切换,可以调用Thread.yield()方法。 这为调度程序提供了一个提示,即当前线程愿意让CPU支持其他线程。 如javadoc所述,这只是一个提示,即JVM可以完全忽略该提示并进一步执行当前线程。 但是出于测试目的,可以使用此方法引入更多上下文切换,从而引发竞争条件等。

测试性能

除了类的正确行为之外,另一方面是它的性能。 在许多实际应用中,性能是一项重要要求,因此必须进行测试。

我们可以利用ExecutorService设置不同数量的线程。 每个线程都应该将一个元素插入到我们的队列中,然后再从中获取它。 在外部循环中,我们增加线程数以查看线程数如何影响吞吐量。

@Testpublic void testPerformance() throws InterruptedException {for (int numThreads = 1; numThreads < THREADS_MAX; numThreads++) {long startMillis = System.currentTimeMillis();final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);for (int i = 0; i < numThreads; i++) {threadPool.submit(new Runnable() {public void run() {for (long i = 0; i < ITERATIONS; i++) {int nextInt = ThreadLocalRandom.current().nextInt(100);try {queue.put(nextInt);nextInt = queue.get();} catch (InterruptedException e) {e.printStackTrace();}}}});}threadPool.shutdown();threadPool.awaitTermination(5, TimeUnit.MINUTES);long totalMillis = System.currentTimeMillis() - startMillis;double throughput = (double)(numThreads * ITERATIONS * 2) / (double) totalMillis;System.out.println(String.format("%s with %d threads: %dms (throughput: %.1f ops/s)", LinkedBlockingQueue.class.getSimpleName(), numThreads, totalMillis, throughput));}}

为了了解我们的简单队列实现的性能,我们可以将其与JDK中的实现进行比较。 一种候选方法是LinkedBlockingQueue 。 它的两个方法put()take()工作方式与我们的实现类似,期望LinkedBlockingQueue有选择地受限制,因此必须跟踪所插入元素的数量,并在队列已满时让当前线程进入Hibernate状态。 此功能需要额外的簿记和插入操作检查。 另一方面,JDK实现不使用同步块,并且已经通过繁琐的性能测量来实现。

当我们使用LinkedBlockingQueue实现与上述相同的测试用例时,两个测试用例均获得以下输出:

图1

图1

该图清楚地表明,对于LinkedBlockingQueue实现,吞吐率(即,每个时间单位执行的操作数)几乎是其两倍。 但是它也表明,只有一个线程,两种实现每秒执行大约相同数量的操作。 添加更多线程可提高吞吐量,尽管曲线很快会针对其饱和值收敛。 添加更多线程不会再提高应用程序的性能。

3.测试框架

您可以查看可用的测试框架,而不是编写自己的框架来为应用程序实现多线程测试用例。 本节重点介绍其中的两个:JMock和Grobo Utils。

JMock

为了进行压力测试,模拟框架JMock提供了Blitzer类。 该类实现的功能与我们在“测试正确性”部分中所做的类似,因为它在内部设置了一个ThreadPool ,执行某些特定操作的任务提交给该ThreadPool 。 您提供要执行的任务/动作的数量以及构造函数的线程数量:

Blitzer blitzer = new Blitzer(25000, 6);

此实例具有方法blitz() ,您只需向其提供Runnable接口的实现:

@Testpublic void stressTest() throws InterruptedException {final SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();blitzer.blitz(new Runnable() {public void run() {try {queue.put(42);queue.get();} catch (InterruptedException e) {e.printStackTrace();}}});assertThat(queue.getSize(), is(0));}

因此,与使用ExecutorService相比, Blitzer类使压力测试的实现更加简单。

Grobo Utils

Grobo Utils是一个框架,为测试多线程应用程序提供支持。 这篇文章描述了该框架背后的思想。

与前面的示例类似,我们拥有类MultiThreadedTestRunner ,该类在内部构造线程池并以给定数量的Runnable实现作为单独的线程执行。 Runnable实例必须实现一个称为TestRunnable的特殊接口。 值得一提的是,它的唯一方法runTest()会引发异常。 这样,线程内抛出的异常会影响测试结果。 当我们使用普通的ExecutorService时,情况并非如此。 这里的任务必须实现Runnable并且它的唯一方法run()不会引发任何异常。 这些任务中引发的异常将被吞没,并且不会破坏测试。

构建完MultiThreadedTestRunner之后,我们可以调用其runTestRunnables()方法并提供在测试失败之前应等待的毫秒数。 最后, assertThat()调用将验证队列再次为空,因为所有测试线程都已删除了先前添加的元素。

public class SimpleBlockingQueueGroboUtilTest {private static class MyTestRunnable extends TestRunnable {private SimpleBlockingQueue<Integer> queue;public MyTestRunnable(SimpleBlockingQueue<Integer> queue) {this.queue = queue;}@Overridepublic void runTest() throws Throwable {for (int i = 0; i < 1000000; i++) {this.queue.put(42);this.queue.get();}}}@Testpublic void stressTest() throws Throwable {SimpleBlockingQueue<Integer> queue = new SimpleBlockingQueue<Integer>();TestRunnable[] testRunnables = new TestRunnable[6];for (int i = 0; i < testRunnables.length; i++) {testRunnables[i] = new MyTestRunnable(queue);}MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(testRunnables);mttr.runTestRunnables(2 * 60 * 1000);assertThat(queue.getSize(), is(0));}
}

翻译自: https://www.javacodegeeks.com/2015/09/testing-concurrent-applications.html

web应用程序并发测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337452.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解存储器层次结构

点击蓝字关注我们1概述对于一个简单的计算机系统模型&#xff0c;我们可以将存储器系统看做是一个线性的字节数组&#xff0c;而 CPU 能够在一个常数时间内访问每个存储器的位置。实际上&#xff0c;存储器系统&#xff08;memory system&#xff09;是一个具有不同容量、成本和…

C++的一个指针占内存几个字节?

C的一个指针占内存几个字节&#xff1f;结论&#xff1a; 取决于是64位编译模式还是32位编译模式&#xff08;注意&#xff0c;和机器位数没有直接关系&#xff09; 在64位编译模式下&#xff0c;指针的占用内存大小是8字节在32位编译模式下&#xff0c;指针占用内存大小是4字…

【C语言】指针进阶第五站:函数指针!

点击蓝字关注我们函数指针函数也有自己的地址&#xff0c;函数名/&函数名 就是函数的地址1.1基本形式在 数组指针的学习中我们了解到int arr[5]; int (*pa)[5] &arr;//pa是数组指针指针变量pa的类型是int(*)[5]那么函数指针的形式是怎样的呢&#xff1f;void test(cha…

jsp 体检信息查询 绕过用户名验证_一篇彻底搞懂jsp

jsp 实栗 jsp jdbc 实现登录实现思路一个表单页&#xff0c;输入用户登录和密码&#xff0c;然后信息提交到jsp页面进行验证&#xff0c;如果可以服务器跳转到登录成功页&#xff0c;失败&#xff0c;跳转到错误页跳转的时候窗口的URL地址会发生变化代码如下编写登录代码登录&…

C/C++与汇编混合编程有什么好处?

点击蓝字关注我们1 导语 当需要C/C与汇编混合编程时&#xff0c;可以有以下两种处理策略&#xff1a;若汇编代码较短&#xff0c;则可在C/C源文件中直接内嵌汇编语言实现混合编程。若汇编代码较长&#xff0c;可以单独写成汇编文件&#xff0c;最后以汇编文件的形式加入项目中&…

centos 7.6安装java_Hadoop的安装

为了方便后面使用Hadoop的shell命令&#xff0c;我先介绍Hadoop的安装。Hadoop有多种安装模式&#xff0c;这里介绍伪分布式的安装。我测试过Ubutun、Centos和WSL&#xff0c;都可以正常安装Hadoop的所有版本。所有一般不会出现版本对应的问题。Hadoop是基于Java语言进行编写的…

C++软件分析师异常分析工作经验汇总

点击蓝字关注我们最近几年工作当中很大一部分内容是排查软件运行过程中遇到的各种异常&#xff0c;积累了一定的经验&#xff0c;在此给大家分享一下。本文将详细讲述Windows系统中软件异常的分类以及常用的排查方法&#xff0c;给大家提供一个借鉴与参考。1、软件异常的分类常…

java fix_Java中的低延迟FIX引擎

java fix总览 Chronicle FIX是我们的Low Latency FIX引擎和Java数据库。 是什么使它与众不同&#xff1f; 是为Java中的超低GC *设计的。 支持字符串和日期时间的方式可以最大程度地减少垃圾和开销。 可自定义为仅包含您期望的字段。 使用通常在二进制解析器和生成器中使用…

linux 查看防火墙状态_每天五分钟学习Linux系列之 - 系统安全配置

20年IT从业&#xff0c;二哥的团队使用最多的系统就是Linux&#xff0c;开发&#xff0c;运维的小伙伴们都离不开Linux系统&#xff0c;特别是大数据和人工智能领域更是如此&#xff0c;但由于日常工作忙&#xff0c;小伙伴们没有太多成块的时间系统的学习Linux, 并且现版CentO…

C++红黑树模拟实现map和set

点击蓝字关注我们一、红黑树及其节点的设计对于底层都是红黑树的map和set来说&#xff0c;他们之间存在的最大的区别就是&#xff1a;对于set是K模型的容器&#xff0c;而map是KV模型的容器。为了更好的灵活兼容实现map和set&#xff0c;就需要在红黑树以及树节点上进行特别的设…

c语言连接mysql_聊聊数据库MySQL、SqlServer、Oracle的区别,哪个更适合你?

一、MySQL优点&#xff1a;体积小、速度快、总体拥有成本低&#xff0c;开源&#xff1b;支持多种操作系统&#xff1b;是开源数据库&#xff0c;提供的接口支持多种语言连接操作 &#xff1b;MySQL的核心程序采用完全的多线程编程。线程是轻量级的进程&#xff0c;它可以灵活地…

绩效工作流_流绩效–您的想法

绩效工作流上周&#xff0c;我介绍了一些有关Java 8流性能的基准测试结果。 你们和gal足够有兴趣留下一些想法&#xff0c;还有哪些可以介绍。 这就是我所做的&#xff0c;这里是结果。 总览 最后一篇文章的序言也适用于此。 阅读它&#xff0c;以找出所有数字为何撒谎&#…

C语言访问MCU寄存器的两种方式

点击蓝字关注我们单片机的特殊功能寄存器SFR&#xff0c;是SRAM地址已经确定的SRAM单元&#xff0c;在C语言环境下对其访问归纳起来有两种方法。1、采用标准C的强制类型转换和指针来实现采用标准C的强制转换和指针的概念来实现访问MCU的寄存器&#xff0c;例如:#define DDRB (*…

08_优先队列

08_优先队列 一、优先队列最大优先队列最大优先队列API设计 最小优先队列最小优先队列API设计最小优先队列代码实现 索引优先队列索引优先队列实现思路索引优先队列API设计索引优先队列代码实现 一、优先队列 :::info 普通的队列是一种先进先出的数据结构&#xff0c;元素在队…

Python3实现红黑树[下篇]

Python3实现红黑树[下篇]我写的红黑树的上篇在这里&#xff1a;https://blog.csdn.net/qq_18138105/article/details/105190887 这是我近期看的文章 https://www.cnblogs.com/gcheeze/p/11186806.html 我看了很多关于红黑树删除的文章和博客&#xff0c;介绍得是相当相当的复…

C语言内存泄露很严重,如何应对?

点击蓝字关注我们1. 前言最近部门不同产品接连出现内存泄漏导致的网上问题&#xff0c;具体表现为单板在现网运行数月以后&#xff0c;因为内存耗尽而导致单板复位现象。**一方面&#xff0c;内存泄漏问题属于低级错误&#xff0c;此类问题遗漏到现网&#xff0c;影响很坏&…

python发送邮件outlook_通过Python发送Outlook电子邮件?

I am using Outlook 2003. What is the best way to send email (through Outlook 2003) using Python? 解决方案 For a solution that uses outlook see TheoretiCALs answer below. Otherwise, use the smtplib that comes with python. Note that this will require your e…

C++队列queue用法详解(超详细)

点击蓝字关注我们一、定义queue是一种容器转换器模板&#xff0c;调用#include< queue>即可使用队列类。二、queue初始化queue<Type, Container> (<数据类型&#xff0c;容器类型>&#xff09;初始化时必须要有数据类型&#xff0c;容器可省略&#xff0c;省…

python随机抽取人名_python实现艾宾浩斯背单词功能,实现自动提取单词、邮件发送,再也不用担心背单词啦...

&#xfeff;已经完成了利用python爬虫实现定时QQ邮箱推送英文文章&#xff0c;辅助学习英语的项目&#xff0c;索性就一口气利用python多做一些自动化辅助英语学习的项目&#xff0c;对自己的编程能力和英文水评也有一定的帮助&#xff0c;于是在两天的努力下&#xff0c;我完…

用不到125行C语言代码就可以编写一个简单的16位虚拟机?

点击蓝字关注我们一位国外的软件工程师分享了这么一篇博文&#xff1a;Writing a simple 16 bit VM in less than 125 lines of C&#xff08;用不到 125 行 C 语言编写一个简单的 16 位虚拟机&#xff09;。博文地址&#xff1a;https://www.andreinc.net/2021/12/01/writing-…