线程同步,线程不同步_重新同步多线程集成测试

线程同步,线程不同步

我最近在Captain Debug的Blog上偶然发现了一篇文章“ 同步多线程集成测试 ”。 那篇文章强调了设计涉及异步运行业务逻辑的被测类的集成测试的问题。 给出了这个人为的示例(我删除了一些评论):

public class ThreadWrapper {public void doWork() {Thread thread = new Thread() {@Overridepublic void run() {System.out.println("Start of the thread");addDataToDB();System.out.println("End of the thread method");}private void addDataToDB() {// Dummy Code...try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}}};thread.start();System.out.println("Off and running...");}}

这只是将业务逻辑委托给我们无法控制的某些异步作业池的常见模式的示例。 Roger Hughes (作者)列举了测试这种代码的几种技巧,包括:

  • 测试方法中的任意(“足够长”) sleep()以确保后台逻辑完成
  • 重构doWork() ,使其接受CountDownLatch并同意在作业完成时通知它
  • 将上述方法@VisibleForTesting包私有且仅@VisibleForTesting
  • “该”解决方案–重构doWork()使其可以接受任意Runnable 。 在测试中,我们可以包装此Runnable (装饰器模式)并等待内部Runnable完成

最后一个解决方案不错,但是它极大地改变了ThreadWrapper的职责。 现在,由调用者决定在先前ThreadWrapper完全封装业务逻辑时应异步执行哪种作业。 我并不是说这是一个不好的设计,但是它与原始方法有很大的不同。

待命性

如果不进行如此大规模的重构,我们可以编写测试吗? 第一个解决方案涉及称为Awaitility的便捷库。 该库不是灵丹妙药,它只是定期评估给定条件并确保它在给定时间内得到满足。 您可能会编写一两次这样的代码,然后将它们包装在具有精心设计的API的库中。 因此,这是我们的初始方法:

import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;//...await().atMost(10, SECONDS).until(recordInserted());//...private Callable<Boolean> recordInserted() {return new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return dataExists();}};
}

我认为这里没有什么可解释的。 dataExists()只是一个boolean方法,该方法最初返回false但是一旦完成后台任务( addDataToDB() ),最终将返回true 。 换句话说,我们假设后台任务引入了一些副作用,而dataExists()可以检测到该副作用。 顺便说一句,我碰巧安装了具有Lambda支持的JDK 8,而IntelliJ IDEA给了我这个很好的工具提示:



突然,我得到了建议的与Java 8兼容的替代方案:

private Callable<Boolean> recordInserted() {return () -> dataExists();
}

但是还有更多:

将我的代码转换为:

private Callable<Boolean> recordInserted() {return this::dataExists;
}

this::前缀表示recordInsterted是当前对象的方法。 我们也可以说someDao::dataExists 。 简单地将此语法turns方法放入我们可以传递的函数对象中(此过程在Scala中称为eta扩展 )。 现在,不再需要recordInsterted()方法,因此我可以内联它并将其完全删除:

await().atMost(10, SECONDS).until(this::dataExists);

我不确定我更喜欢什么-新的lambda语法或IntelliJ IDEA如何采用Java 8之前的代码并自动为我进行改版 (嗯,这仍然有点试验性,刚刚报道了IDEA-106670 )。 我可以在IntelliJ项目级的Lambda中运行此意图,从而在几秒钟内启用整个代码库。 甜!

但是回到原来的问题。 通过提供体面的API和一些方便的功能,Awaitility很有帮助。 我将它与FluentLenium广泛结合使用。 但是定期轮询状态变化感觉有点像变通办法,并且仍然引入了最小的延迟。 但是请注意,在异步任务上运行和同步非常普遍,并且JDK已经提供了必要的功能: Future抽象 !

java.util.concurrent.Future

为了限制重构的范围,我现在暂时保留原始的new Thread()方法,并使用Guava中的SettableFuture<V> 。 它是Future<V>实现,允许在任何时间从任何线程触发完成或失败(请参阅DeferredResult – Spring MVC中的异步处理以获取更多高级用法)。 如您所见,更改非常小:

public class ThreadWrapper {public ListenableFuture<Void> doWork() {final SettableFuture<Void> future = SettableFuture.<Void>create();Thread thread = new Thread() {@Overridepublic void run() {addDataToDB()//...//last instructionfuture.set(null);}private void addDataToDB() {// Dummy Code...// ...}};thread.start();return future;}}

doWork()现在返回在异步任务内部具有生命周期控制的ListenableFuture<Void> 。 我们使用Void但实际上您可能想返回一些异步结果。 future.set(null)调用至关重要。 它表示将来已实现,并且将通知所有等待该将来的线程。 再一次,在实践中,您将使用例如Future<Integer> ,然后我们会说future.set(someInteger)而不是null 。 这里null只是Void类型的占位符。 这对我们有什么帮助? 测试代码现在可以依赖将来的完成:

final ListenableFuture<Void> future = wrapper.doWork();
future.get(10, SECONDS);

future.get()阻塞,直到将来完成(超时),即直到我们调用future.set(...)为止。 顺便说一句,我使用的是Guava的ListenableFuture ,但是Java 8引入了等效和标准的CompletableFuture –我将很快写它。

所以,我们到了某个地方。 Future<T>是用于等待和发信号通知后台作业完成的有用抽象。 但也有一个巨大的优势, Future未服用,ekhm,从优势-异常处理和传播。 Future.get()将阻塞,直到将来完成,并返回异步结果引发最初从我们的工作中引发的异常。 这对于异步测试非常有用。 当前,如果Thread.run()引发异常,则它可能会记录也可能不会记录或对我们可见,并且将来将永远无法完成。 有了Awaitility,它会稍微好一点-它将超时而没有任何有意义的原因,必须在控制台/日志中手动进行跟踪。 但是,只需稍作修改,我们的测试就会更加冗长:

public void run() {try {addDataToDB()//...future.set(null);} catch (Exception e) {future.setException(e);}
}

如果异步作业中发生某些异常,它将弹出并显示为JUnit / TestNG失败原因。

(听)ExecutorService

而已。 如果addDataToDB()引发异常,它将不会丢失。 相反,测试中的future.get()将为我们重新抛出该异常。 我们的测试不会只是超时,也不会让我们知道发生了什么问题。 太好了,但是我们真的必须创建这个特殊的SettableFuture<T>实例,难道我们不能仅使用已经为我们提供Future<T>并具有正确基础实现的现有库吗? 当然! 这样就需要进一步重构:

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class ThreadWrapper {private final ListeningExecutorService executorService =MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());public ListenableFuture<?> doWork() {Runnable job = new Runnable() {@Overridepublic void run() {//...}};return executorService.submit(job);}}

这就是你们一直在等待的东西。 不要一直启动新Thread ,请使用线程池! 实际上,我通过使用ListeningExecutorService (对ExecutorService的扩展)又走了一步,该扩展返回了ListenableFuture实例( 请参阅为什么 ListenableFuture )。 但是解决方案不需要这样做,我只是传播了良好的做法。 如您所见,现在已经为我们创建并管理了Future实例。 测试完全相同,但是生产代码更简洁,更可靠。

MoreExecutors.sameThreadExecutor()

我想向您展示的最后一个技巧是依赖注入。 首先,让我们从ThreadWrapper类外部化线程池的创建:

private final ListeningExecutorService executorService;public ThreadWrapper() {this(Executors.newSingleThreadExecutor());
}public ThreadWrapper(ExecutorService executorService) {this.executorService =MoreExecutors.listeningDecorator(executorService);
}

现在,我们可以选择提供自定义ExecutorService 。 出于其他各种原因,这是件好事,但对我们来说,这提供了全新的测试机会: MoreExecutors.sameThreadExecutor() 。 这次我们稍微修改一下测试:

final ThreadWrapper wrapper = new ThreadWrapper(MoreExecutors.sameThreadExecutor());
wrapper.doWork().get();

看看我们如何通过自定义ExecutorService ? 这是一个非常特殊的实现,它实际上并不维护任何类型的线程池。 每次您向该“池” submit()一些任务时,它将以阻塞方式在同一线程中执行。 这意味着即使生产代码没有太大变化,我们也不再需要异步测试! wrapper.doWork()将阻塞,直到“后台”作业完成。 仍然需要对get()额外的调用,以确保传播了异常,但保证永远不会阻塞(因为作业已经完成)。

如果您某种程度上依赖于基于线程的属性(例如事务,安全性, ThreadLocal ,则使用同一线程而不是线程池来执行异步任务可能会产生意外结果。 但是,如果您将标准ThreadPoolExecutorCallerRunsPolicy一起CallerRunsPolicy ,则在线程池溢出的情况下,JDK会以这种方式运行。 因此,这并不稀奇。

摘要

测试异步代码很困难,但是您可以选择。 几种选择。 但是令我印象深刻的一个结论是我们努力的副作用。 我们重构了原始代码以使其可测试。 但是最终的生产代码不仅可以测试,而且结构和健壮性也更好。 令人惊讶的是,它甚至与以前的版本兼容,因为我们几乎没有将返回类型从void更改为Future<Void>

这似乎是一条规则-可测试的代码通常可以更好地设计和实现。 单元测试是使用我们库的第一个客户端代码。 它自然会迫使我们多考虑消费者,而不是实现。

参考:从Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz 重新审视了同步多线程集成测试 。

翻译自: https://www.javacodegeeks.com/2013/05/synchronising-multithreaded-integration-tests-revisited.html

线程同步,线程不同步

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

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

相关文章

无限滚动重置服务器,简单无限滚动的实现

在使用elementUI组件库的时候&#xff0c;用到了无限滚动这个功能。我没有看源码&#xff0c;直接在网上学习了下实现的思路&#xff0c;然后自己手动编码以下。在此总结下。假设页面上有一个盒子容器&#xff0c;容器内有一些子元素。容器的高度是固定的&#xff0c;有纵向滚动…

Fatjars,Thinwars以及为什么OpenLiberty很酷

法特哈斯 构建一个Fatjar&#xff08;或Uberjar&#xff09;&#xff0c;其中包含将应用程序很好地打包在一起运行所需的一切&#xff0c;这意味着您可以&#xff1a; java -jar myapp.jar然后离开。 没有应用程序服务器。 没有类路径。 这种方法已经被诸如Springboot之类的微…

Spring Cloud Config Server简介

1.概述 在本教程中&#xff0c;我们将回顾Spring Cloud Config Server的基础知识。 我们将设置一个Config Server &#xff0c;然后构建一个客户端应用程序 &#xff0c;该客户端应用程序在启动时会消耗配置 &#xff0c;然后刷新配置而不重新启动。 我们正在构建的应用程序与《…

朴素贝叶斯算法实现分类以及Matlab实现

开始 其实在学习机器学习的一些算法&#xff0c;最近也一直在看这方面的东西&#xff0c;并且尝试着使用Matlab进行一些算法的实现。这几天一直在看得就是贝叶斯算法实现一个分类问题。大概经过了一下这个过程&#xff1a; 看书→算法公式推演→网上查询资料→进一步理解→搜…

位操作基础篇之位操作全面总结

转载自 http://blog.csdn.net/morewindows/article/details/7354571 Title: 位操作基础篇之位操作全面总结 KeyWord: C/C 位操作 位操作技巧 判断奇偶 交换两数 变换符号 求绝对值 位操作压缩空间 筛素数 位操作趣味应用 位操作笔试面试 位操作篇共分为基础篇和提高…

机器学习中的算法-支持向量机(SVM)基础

机器学习中的算法-支持向量机(SVM)基础 版权声明&#xff1a; 本文由LeftNotEasy发布于http://leftnoteasy.cnblogs.com, 本文可以被全部的转载或者部分使用&#xff0c;但请注明出处&#xff0c;如果有问题&#xff0c;请联系wheeleastgmail.com。也可以加我的微博: leftnotea…

算法题:输入aaaabbbcccccc输出a4b3c6。

今日在地铁上浏览今日头条的时候看到这么个小题目&#xff0c;说是输出一长串字符串&#xff0c;输出字母串类别并且统计其出现次数&#xff0c;然后按照顺序将其输出来。例如输入aaaabbbcccccc&#xff0c;输出a4b3c6。 最近也一直在学习&#xff0c;所以就想着就Matlab来试了…

Java World中的GraphQL简介

许多人认为GraphQL仅适用于前端和JavaScript&#xff0c;它在Java等后端技术中没有定位&#xff0c;但事实确实如此。 还经常将GraphQL与REST进行比较&#xff0c;但是这种比较是否合理&#xff1f; 首先&#xff0c;让我开始回答其中最重要的问题。 什么是GraphQL&#xff1…

算法题:在一个字符串中找到只出现一次的字符。如输入abaccdeeff,则输出bd。

今天的算法学习还是和字符串有关&#xff0c;这个题目据说是以前的某公司面试的笔试题目。题目意思就是说&#xff0c;在一个字符串中找到只出现了一次的那些字符&#xff0c;并且输出来。 作为非IT的我&#xff0c;平时使用Matlab比较多。不是科班出身&#xff0c;对于这个题…

Kafka的Spring Cloud Stream

总览 该示例项目演示了如何使用事件驱动的体系结构 &#xff0c; Spring Boot &#xff0c;Spring Cloud Stream&#xff0c; Apache Kafka和Lombok构建实时流应用程序。 在本教程结束时&#xff0c;您将运行一个简单的基于Spring Boot的Greetings微服务 从REST API获取消息 …

常见的股票技术因子学习以及计算

最近在看《量化投资数据挖掘技术与实践&#xff08;MATLAB版&#xff09;》。学习了其中的常见的股票衍生变量&#xff0c;并且利用WIND金融数据终端的matlab借口windmatlab导出一些数据进行了一个简单的学习。特此记录。 下面是我对于书中提到的几个因子的学习总结&#xff1…

Java – HashMap详细说明

HashMap基于哈希算法工作&#xff0c;根据Java文档HashMap具有以下四个构造函数&#xff0c; 建设者 描述 HashMap ​() 构造一个空的 具有默认初始容量&#xff08;16&#xff09;和默认加载因子&#xff08;0.75&#xff09;的HashMap 。 HashMap ​(int initialCapaci…

Python实现石头-剪刀-布小游戏

近日在学习Python的一些基础知识&#xff0c;觉得还是很有趣的一个一门语言&#xff01;就目前的学习的一些知识&#xff0c;编写了一些一个简单的石头剪刀布的游戏。主要是熟悉一些Python的一些控制语句。 import random while 1:sint(random.randint(1,3))print(s)print()if…

Python:递归输出斐波那契数列

今天学习Python的时候做一道练习题&#xff0c;题目是这样的&#xff1a; 题目 导入 问题 有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c;小兔子长到第三个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c;问每个月的兔子总对数为多…

排序算法二:快速排序算法原理以及MATLAB与Python实现

今天继续学习排序算法。今天的主角是快速排序算法。 1. 快速排序基本原理 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略&#xff0c;通常称其为分治法(Divide-and-ConquerMethod)。 该方法的基本思想是&#xff1a; 1&#xff0e;先从数列…

排序算法三:堆排序基本原理以及Python实现

1. 基本原理 堆排序就是利用堆的特性进行一个无序序列的排序工作。 堆的特点 堆分为最大堆和最小堆&#xff0c;其实就是完全二叉树。 最大堆要求节点的元素都要不小于其孩子最小堆要求节点元素都不大于其左右孩子。 两者对左右孩子的大小关系不做任何要求&#xff0c;其实…

spring jms 消息_Spring JMS,消息自动转换,JMS模板

spring jms 消息在我的一个项目中&#xff0c;我应该创建一个消息路由器&#xff0c;就像所有路由器一样&#xff0c;它应该从一个主题获取JMS消息并将其放入另一个主题。 该消息本身是JMS文本消息&#xff0c;实际上包含XML消息。 收到消息后&#xff0c;我还应该添加一些其他…

排序算法四:归并排序基本原理以及Python实现

1. 基本原理 归并排序建立在归并操作上的一种算法。该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。归并排序是将两 个已经有序的序列合成一个有序的序列的过程。 因此&#xff0c;对于一个待排序的序列来说&#xff0c;首先要将其进行…

如何将JAR添加到Jetbrains MPS项目

Jetbrains MPS是创建DSL的绝佳工具。 我们喜欢它&#xff0c;并在我们的咨询工作中定期使用它。 因此&#xff0c;我们之前已经写过关于Jetbrains MPS的文章 。 作为投影编辑器&#xff0c;您可以轻松创建可通过图形界面或数学公式之类使用的DSL。 尽管所有这些功能都需要做一…

Python 3实现k-邻近算法以及 iris 数据集分类应用

前言 这个周基本在琢磨这个算法以及自己利用Python3 实现自主编程实现该算法。持续时间比较长&#xff0c;主要是Pyhton可能还不是很熟练&#xff0c;走了很多路&#xff0c;基本是一边写一边学。不过&#xff0c;总算是基本搞出来了。不多说&#xff0c;进入正题。 1. K-邻近…