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;是一个具有不同容量、成本和…

python中可选参数是什么意思_Python如何定义有可选参数的元类

问题 你想定义一个元类&#xff0c;允许类定义时提供可选参数&#xff0c;这样可以控制或配置类型的创建过程。 解决方案 在定义类的时候&#xff0c;Python允许我们使用 metaclass关键字参数来指定特定的元类。 例如使用抽象基类&#xff1a; from abc import ABCMeta, abstra…

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

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

jmc线程转储_使线程转储智能化

jmc线程转储很久以前&#xff0c;我了解了一个称为Log MDC的东西&#xff0c;我对此非常感兴趣。 我突然意识到日志文件中发生的一切&#xff0c;并指出了特定的日志条目&#xff0c;并找到了对错&#xff0c;特别是在调试生产中的错误时。 2013年&#xff0c;我受委托从事一个…

【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地址会发生变化代码如下编写登录代码登录&…

Python3求解找到小镇的法官问题

Python3求解找到小镇的法官问题原题 https://leetcode-cn.com/problems/find-the-town-judge/题目&#xff1a; 在一个小镇里&#xff0c;按从 1 到 N 标记了 N 个人。传言称&#xff0c;这些人中有一个是小镇上的秘密法官。 如果小镇的法官真的存在&#xff0c;那么&#xff…

couchbase_具有Rx-Java的Couchbase Java SDK

couchbase关于Couchbase Java SDK的一件整洁的事情是&#xff0c;它建立在出色的Rx-Java库的基础上&#xff0c;这为与Couchbase服务器实例进行交互提供了一种React性的方式&#xff0c;一旦掌握了它&#xff0c;它就非常直观。 考虑一个我打算存储在Couchbase中的非常简单的j…

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语言进行编写的…

Python3 解题:字符串压缩

Python3 解题&#xff1a;字符串压缩原题 https://leetcode-cn.com/problems/compress-string-lcci/题目&#xff1a; 字符串压缩。利用字符重复出现的次数&#xff0c;编写一种方法&#xff0c;实现基本的字符串压缩功能。比如&#xff0c;字符串aabcccccaaa会变为a2b1c5a3。若…

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…

Python3求解旋转矩阵问题

Python3求解旋转矩阵问题原题 https://leetcode-cn.com/problems/spiral-matrix/ 给定一个包含 m x n 个元素的矩阵&#xff08;m 行, n 列&#xff09;&#xff0c;请按照顺时针螺旋顺序&#xff0c;返回矩阵中的所有元素。 示例 1: 输入: [[ 1, 2, 3 ],[ 4, 5, 6 ],[ 7, 8…

只针对异常的情况才使用异常_如何以及何时使用异常

只针对异常的情况才使用异常本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看…

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;它可以灵活地…

Python3解题:二叉树路径总和问题

Python3解题&#xff1a;二叉树路径总和问题原题 https://leetcode-cn.com/problems/path-sum-ii/ 给定一个二叉树和一个目标和&#xff0c;找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 给定如下二叉树&#xff…

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

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