java 线程安全性_我如何测试Java类的线程安全性

java 线程安全性

我在最近的一次网络研讨会中谈到了这个问题,现在是时候以书面形式进行解释了。 线程安全是Java等语言/平台中类的重要品质,在Java中我们经常在线程之间共享对象。 缺乏线程安全性导致的问题很难调试,因为它们是零星的,几乎不可能有意复制。 您如何测试对象以确保它们是线程安全的? 这就是我的做法。

女人的香气(1992),马丁·布雷斯特(Martin Brest)

我们说有一个简单的内存书架:

class Books {final Map<Integer, String> map =new ConcurrentHashMap<>();int add(String title) {final Integer next = this.map.size() + 1;this.map.put(next, title);return next;}String title(int id) {return this.map.get(id);}
}

首先,我们将一本书放在那里,书架返回其ID。 然后,我们可以通过ID读取该书的标题:

Books books = new Books();
String title = "Elegant Objects";
int id = books.add(title);
assert books.title(id).equals(title);

该类似乎是线程安全的,因为我们使用的是线程安全的ConcurrentHashMap而不是更原始的和非线程安全的HashMap ,对吧? 让我们尝试测试一下:

class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();String title = "Elegant Objects";int id = books.add(title);assert books.title(id).equals(title);}
}

测试通过了,但这只是一个单线程测试。 让我们尝试从几个并行线程中进行相同的操作(我正在使用Hamcrest ):

class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();int threads = 10;ExecutorService service =Executors.newFixedThreadPool(threads);Collection<Future<Integer>> futures =new LinkedList<>();for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> books.add(title)));}Set<Integer> ids = new HashSet<>();for (Future<Integer> f : futures) {ids.add(f.get());}assertThat(ids.size(), equalTo(threads));}
}

首先,我通过Executors创建线程池。 然后,我通过submit()提交十个Callable类型的对象。 他们每个人都会在书架上添加一本独特的新书。 所有这些线程将由池中的那十个线程中的某些线程以某种不可预测的顺序执行。

然后,我通过Future类型的对象列表获取其执行者的结果。 最后,我计算创建的唯一图书ID的数量。 如果数字为10,则没有冲突。 我使用Set集合来确保ID列表仅包含唯一元素。

测试通过了我的笔记本电脑。 但是,它不够坚固。 这里的问题是,它不是从多个并行线程真正测试“ Books 。 我们调用之间经过的时间submit()是足够大的,完成的执行books.add() 这就是为什么实际上只有一个线程可以同时运行的原因。 我们可以通过修改一些代码来检查它:

AtomicBoolean running = new AtomicBoolean();
AtomicInteger overlaps = new AtomicInteger();
Collection<Future<Integer>> futures = new LinkedList<>();
for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> {if (running.get()) {overlaps.incrementAndGet();}running.set(true);int id = books.add(title);running.set(false);return id;}));
}
assertThat(overlaps.get(), greaterThan(0));

通过此代码,我试图查看线程相互重叠的频率并并行执行某些操作。 这永远不会发生,并且overlaps等于零。 因此,我们的测试尚未真正完成任何测试。 它只是在书架上一一增加了十本书。 如果我将线程数量增加到1000,它们有时会开始重叠。 但是,即使它们数量很少,我们也希望它们重叠。 为了解决这个问题,我们需要使用CountDownLatch

CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean running = new AtomicBoolean();
AtomicInteger overlaps = new AtomicInteger();
Collection<Future<Integer>> futures = new LinkedList<>();
for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> {latch.await();if (running.get()) {overlaps.incrementAndGet();}running.set(true);int id = books.add(title);running.set(false);return id;}));
}
latch.countDown();
Set<Integer> ids = new HashSet<>();
for (Future<Integer> f : futures) {ids.add(f.get());
}
assertThat(overlaps.get(), greaterThan(0));

现在,每个线程在接触书本之前,都要等待latch给予的许可。 当我们通过submit()提交所有内容时,它们将保持等待状态。 然后,我们使用countDown()释放闩锁,它们同时开始运行。 现在,在我的笔记本电脑上,即使threads为10, overlaps也等于3-5。

最后的assertThat()现在崩溃了! 我没有像以前那样得到10个图书ID。 它是7-9,但绝不是10。显然,该类不是线程安全的!

但是在修复该类之前,让我们简化测试。 让我们用RunInThreads从Cactoos ,这确实是我们在前面已经做了完全一样的,但引擎盖下:

class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();MatcherAssert.assertThat(t -> {String title = String.format("Book #%d", t.getAndIncrement());int id = books.add(title);return books.title(id).equals(title);},new RunsInThreads<>(new AtomicInteger(), 10));}
}

assertThat()的第一个参数是Func (功能接口)的实例,它接受AtomicIntegerRunsInThreads的第一个参数)并返回Boolean 。 使用与上述相同的基于闩锁的方法,此功能将在10个并行线程上执行。

这个RunInThreads似乎紧凑且方便,我已经在一些项目中使用它。

顺便说一句,为了使Books线程安全性,我们只需要向其方法add()添加synchronized 。 或者,也许您可​​以提出更好的解决方案?

翻译自: https://www.javacodegeeks.com/2018/03/how-i-test-my-java-classes-for-thread-safety.html

java 线程安全性

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

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

相关文章

java asin_Java asin()方法

Java asin()方法asin() 方法用于返回指定double类型参数的反正弦值。语法doubleasin(doubled)参数 d -- 任何原生数据类型。返回值返回指定double类型参数的反正弦值。实例public class Test{public static void main(String args[]){double degrees 45.0;double radians Mat…

MacOS 的软件包管理工具 HomeBrew 详解

文章目录一、软件介绍二、安装 Homebrew使用 Ruby 或者 Shell 脚本安装手动 clone 安装三、卸载 Homebrew四、Homebrew 安装目录五、安装包所在位置六、程序安装目录七、软件版本切换八、清除旧版本文件九、brew 命令汇总安装、升级和删除软件包查看和检验软件包参考示例服务相…

java中的深度克隆浅克隆_了解Java中的可克隆接口

java中的深度克隆浅克隆什么是对象克隆&#xff1f; 对象克隆是生成具有不同名称的对象的精确字段到字段副本的过程。 克隆的对象在内存中有自己的空间&#xff0c;可在其中复制原始对象的内容。 这就是为什么在克隆后更改原始对象的内容时&#xff0c;所做的更改不会反映在克隆…

python env_#!/usr/bin/env python 有什么用?

我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样#!/usr/bin/python或者这样#!/usr/bin/env python那么他们有什么用呢&#xff1f;要理解它&#xff0c;得把这一行语句拆成两部分。第一部分是 #!第二部分是 /usr/bin/python 或者 /usr/bin/env python关于 #! …

MacBook(macOS) 如何安装 Homebrew Cask(作废)

文章目录重要通知&#xff08;不要再去安装 Homebrew Cask 了&#xff09;Homebrew Cask 简介相关文件和目录Homebrew Cask 安装使用命令下载安装直接从官网下载安装包利用国内源手动 clone下载&#xff08;推荐&#xff09;Homebrew Cask 换源重要通知&#xff08;不要再去安装…

java.io.file()_JAVA基础知识之IO-File类

File类介绍File是java.io包下面的一个类&#xff0c;代表与平台无关的文件或者目录。JAVA中&#xff0c;无论文件还是目录&#xff0c;都可以看作File类的一个对象。File类能对文件或目录新建&#xff0c;删除&#xff0c;获取属性等操作&#xff0c;但是不能直接操作文件内容(…

netflix 模式创新_创新设计模式:单例模式

netflix 模式创新单例设计模式是一种软件设计模式&#xff0c;用于将类的实例化限制为一个对象。 与其他创建设计模式&#xff08;例如抽象工厂 &#xff0c; 工厂和构建器模式&#xff09;相比&#xff0c;单例将创建一个对象&#xff0c;但也将负责&#xff0c;因此该对象只…

美式英语 [t] 的发音

爆破音 true [t]&#xff0c;发真实的 t。 单词的开始处重读音节的开始处&#xff0c;且前面没有跟着清辅音 [s] held 住不发声 叫法很多&#xff0c;声门塞音、吞音、喉塞&#xff08;s&#xff09;音、stop [t]&#xff0c;held [t]&#xff0c;不爆破的 [t]。 发音的口…

设计模式 原型模式_创新设计模式:原型模式

设计模式 原型模式原型模式用于创建对象的副本。 这种模式非常有用&#xff0c;特别是当从头开始创建对象的成本很高时。 与builder &#xff0c; factory和abstract factory模式相比&#xff0c;它不会从头开始创建对象&#xff0c;而是会克隆/重新创建它。 与单例模式相比&a…

java file 对象_Java里File对象的问题。

Java里File对象的问题。关注:282 答案:2 mip版解决时间 2021-02-02 07:14提问者鉨瞞着所囿亾&#xff0c;爱着誰2021-02-01 08:59import java.io.*;在此基础上怎么修改呢&#xff1f; 谢谢啦最佳答案二级知识专家山河已春2021-02-01 10:06import java.io.*;class JavaFileLis…

美式英语中 [d] 的特别发音

闪音 flap [d]&#xff0c;fast [d]&#xff0c;闪音 [d] 发音口腔部位说明&#xff1a;舌尖快速拍打上齿龈后&#xff0c;快速释放&#xff0c;轻快拍打 [d] 在两个元音之间发成 fast [d]&#xff0c;非重读音节在元音和 [l] 之间发成 fast [d]&#xff0c;非重读音节在[r]…

netflix 模式创新_创新设计模式:工厂模式

netflix 模式创新以前&#xff0c;我们对创建模式进行了介绍&#xff0c;并使用抽象工厂模式来创建对象族。 下一个模式是Factory模式 。 当涉及到Java时&#xff0c;工厂模式是最常用的模式之一。 那么工厂模式到底是什么呢&#xff1f; 工厂模式处理创建对象而不指定确切的…

g++ 编译mysql动态库_Linux下g++编译以及使用静态库和动态库的方法详解

下面小编就为大家带来一篇Linux下g编译与使用静态库和动态库的方法。小编觉得挺不错的&#xff0c;现在就分享给大家&#xff0c;也给大家做个参考。一起跟随小编过来看看吧在windows环境下&#xff0c;我们通常在IDE如VS的工程中开发C项目&#xff0c;对于生成和使用静态库(*.…

家用电器如何计算功率和消耗的度数

计算公式&#xff1a;((U*I)/1000)*t 说明&#xff1a; 这里计算的都是电器的额定功率、额定电压、额定电流。U 是电压&#xff1b;I 是电流&#xff1b;t 是时间&#xff0c;单位是小时&#xff1b;U*I 得到的是电功率&#xff0c;例如电压12v&#xff0c;电流5A&#xff0c;…

构建器设计模式_创新设计模式:构建器模式

构建器设计模式以前我们看过工厂和抽象工厂模式。 这些模式可以达到目的&#xff0c;并且确实有用&#xff0c;但是在某些用例中&#xff0c;我们必须创建一个非常复杂的对象&#xff0c;并且创建它需要不同的步骤&#xff0c;每个步骤都需要不同的操作。 在这种情况下&#xf…

在线maven托管_使用Amazon s3托管您的Maven工件

在线maven托管如果您使用Amazon Web Services并将Java用于项目&#xff0c;则Amazon S3是托管团队工件的理想场所。 它很容易设置&#xff0c;而且很便宜。 如果您对它们的功能不特别感兴趣&#xff0c;那么它比设置现有存储库选项&#xff08;jfrog&#xff0c;nexus&#xf…

英语单词如何看字母划分音节

音节划分是读准单词的基本前提之一&#xff0c;划分音节虽麻烦但熟能生巧&#xff0c;在英语交流或阅读时&#xff0c;经常会碰到不认识的单词或专有名称&#xff0c;如果你懂得了音节的划分&#xff0c;那再难的单词你也会读。 1、音节划分。 英语中以元音来划分音节&#xf…

layui绑定json_JSON绑定:概述系列

layui绑定jsonJSON绑定的Java API&#xff08;JSON-B&#xff09;1.0增强了Java EE平台对JSON数据交换格式的整体支持。 事实证明&#xff0c; 用于JSON处理的Java API&#xff08;JSON-P&#xff09;1.1十分流行&#xff0c;它们共同构成了完美的合作伙伴&#xff0c;填补了Ja…

英文单词的音节分类

绝对开音节 以发音的元音字母结尾的叫绝对开音节&#xff0c;且这个元音字母发的是自己本音。例如&#xff1a;he /hi:/ 和 go /gəu/ 相对开音节 一个辅音字母加上不发音的元音字母&#xff08;通常是字母 e&#xff09;结尾的叫相对开音节&#xff0c;其实就是以不发音的元…

java捕捉了异常_java 异常捕获与异常处理

try{会产生异常的代码}catch(出现异常的类型 e){异常出现后处理的方法}一旦异常出现又没有异常处理&#xff0c;程序就会中断。public static void main(String[] args) {try {int num 10 / 0;System.out.println(num);} catch (ArithmeticException e) {System.out.println(&…