测试并发应用

本文是我们学院课程中名为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 这个学生或者新用户基本都是一折,还能玩得起。所有软件的安装除了选择安装路径,都可以无脑按安装。 …

[CareerCup] 9.6 Generate Parentheses 生成括号

9.6 Implement an algorithm to print all valid (e.g., properly opened and closed) combinations of n-pairs of parentheses.EXAMPLEInput: 3Output: ((())), (()()), (())(), ()(()), ()()() LeetCode上的原题&#xff0c;请参见我之前的博客Generate Parentheses 生成括号…

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

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、…

ibatis Parameter index out of range (1 number of parameters, which is 0)

这个错误除了网上常见的like写错之外&#xff0c;这里列出其中一种写法like concat(%, #keyword#, %),还有另外的多写单引号什么的以外&#xff0c;今天遇到另一个原因,百度谷歌各种查也没查到&#xff0c;最后才意识到由于我是在navicat中的写好的再粘贴上去的&#xff0c;注释…

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

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

MSSQL提权

之前对MSSQL提权了解较少&#xff0c;所以在这里记录一些关于这类提权的几种姿势 1.xp_cmdshell提权 存储过程为数据库提供了强大的功能&#xff0c;其类似UDF&#xff0c;在MSSQL中xp_cmdshell可谓臭名昭著了。MSSQL强大的存储过程也为黑客提供了遍历&#xff0c;在相应的权…

如何以及为什么序列化Lambda

总览 lambda序列化在许多用例中很有用&#xff0c;例如持久配置或作为远程资源的访客模式 。 远程访客 例如&#xff0c;因此我想访问远程Map上的资源&#xff0c;可以使用get / put&#xff0c;但是说我只想从Map的值中返回一个字段&#xff0c;我可以将lambda作为访问者来传…

NSTimer注意内存泄露(真该死)

NSTimer可以用来执行一些定时任务&#xff0c;比较常用的方法就是&#xff1a; (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; 可是&#xff0c;仔细看官方文档中对于参数t…

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…

Mysql命令alter add:增加表的字段

alter add命令用来增加表的字段。alter add命令格式&#xff1a;alter table 表名 add字段 类型 其他;例如&#xff0c;在表MyClass中添加了一个字段passtest&#xff0c;类型为int(4)&#xff0c;默认值为0&#xff1a; mysql> alter table MyClass add passtest int(4) …

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

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

swaks使用教程

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

具有Rx-Java的Couchbase Java SDK

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

电脑win7支持的node.js版本

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

in-place数据交换

实现in-place的数据交换 声明&#xff1a;引用请注明出处http://blog.csdn.net/lg1259156776/ 经典的排序问题 问题描述 一个数组中包含两个已经排好序的子数组&#xff0c;设计一个in-place&#xff08;原位操作&#xff09;算法来对这个数组排序。测试数据为 a[] 1 4 5 7 8 …

Office-DOC加载宏-上线CS

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