如何测试Java类的线程安全性
这篇文章主要介绍了如何测试Java类的线程安全性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
线程安全性是Java等语言/平台中类的一个重要标准,在Java中,我们经常在线程之间共享对象。由于缺乏线程安全性而导致的问题很难调试,因为它们是偶发的,而且几乎不可能有目的地重现。如何测试对象以确保它们是线程安全的?
假如有一个内存书架
packagecom.mzc.common.thread; importjava.util.Map; importjava.util.concurrent.ConcurrentHashMap; /** **功能:内存书架 * * *@authorMoore *@ClassNameBooks. *@VersionV1.0. *@date2019.12.1014:00:13 */ publicclassBooks{ finalMap map=newConcurrentHashMap<>(); /** * *功能:存书,并返回书的id * * *@paramtitle: *@returnint *@authorMoore *@date2019.12.1014:00:16 */ intadd(Stringtitle){ finalIntegernext=this.map.size()+1; this.map.put(next,title); returnnext; } /** * *功能:根据书的id读取书名 * * *@paramid: *@returnstring *@authorMoore *@date2019.12.1014:00:16 */ Stringtitle(intid){ returnthis.map.get(id); } }
首先,我们把一本书放进书架,书架会返回它的ID。然后,我们可以通过它的ID来读取书名,像这样:
Booksbooks=newBooks(); Stringtitle="ElegantObjects"; intid=books.add(title); assertbooks.title(id).equals(title);
这个类似乎是线程安全的,因为我们使用的是线程安全的ConcurrentHashMap,而不是更原始和非线程安全的HashMap,对吧?我们先来测试一下:
publicclassBooksTest{ @Test publicvoidaddsAndRetrieves(){ Booksbooks=newBooks(); Stringtitle="ElegantObjects"; intid=books.add(title); assertbooks.title(id).equals(title); } }
查看测试结果:
/** **功能:多线程测试 * * *@throwsExecutionExceptiontheexecutionexception *@throwsInterruptedExceptiontheinterruptedexception *@authorMoore *@date2019.12.1014:16:34 */ @Test publicvoidaddsAndRetrieves2()throwsExecutionException,InterruptedException{ Booksbooks=newBooks(); intthreads=10; ExecutorServiceservice=Executors.newFixedThreadPool(threads); Collection >futures=newArrayList<>(threads); for(intt=0;t books.add(title))); } Set ids=newHashSet<>(); for(Future f:futures){ ids.add(f.get()); } assertThat(ids.size(),equalTo(threads)); }
首先,我通过执行程序创建线程池。然后,我通过Submit()提交10个Callable类型的对象。他们每个都会在书架上添加一本唯一的新书。所有这些将由池中的10个线程中的某些线程以某种不可预测的顺序执行。
然后,我通过Future类型的对象列表获取其执行者的结果。最后,我计算创建的唯一图书ID的数量。如果数字为10,则没有冲突。我使用Set集合来确保ID列表仅包含唯一元素。
我们看一下这样改造后的运行结果:
我们可以通过修改一些代码再来检查它:
@Test publicvoidaddsAndRetrieves3(){ Booksbooks=newBooks(); intthreads=10; ExecutorServiceservice=Executors.newFixedThreadPool(threads); AtomicBooleanrunning=newAtomicBoolean(); AtomicIntegeroverlaps=newAtomicInteger(); Collection>futures=newArrayList<>(threads); for(intt=0;t { if(running.get()){ overlaps.incrementAndGet(); } running.set(true); intid=books.add(title); running.set(false); returnid; } ) ); } assertThat(overlaps.get(),greaterThan(0)); }
看一下测试结果:
通过上面的代码,我试图了解线程之间的重叠频率以及并行执行的频率。但是基本上概率为0,所以这个测试还没有真正测到我想测的,还不是我们想要的,它只是把十本书一本一本地加到书架上。
再来:
但是我希望即使线程数只有10个的时候,也会出现重叠并行的情况。怎么办呢?为了解决这个问题,我使用CountDownLatch:
@Test publicvoidaddsAndRetrieves4()throwsExecutionException,InterruptedException{ Booksbooks=newBooks(); intthreads=10; ExecutorServiceservice=Executors.newFixedThreadPool(threads); CountDownLatchlatch=newCountDownLatch(1); AtomicBooleanrunning=newAtomicBoolean(); AtomicIntegeroverlaps=newAtomicInteger(); Collection>futures=newArrayList<>(threads); for(intt=0;t { latch.await(); if(running.get()){ overlaps.incrementAndGet(); } running.set(true); intid=books.add(title); running.set(false); returnid; } ) ); } latch.countDown(); Set ids=newHashSet<>(); for(Future f:futures){ ids.add(f.get()); } assertThat(overlaps.get(),greaterThan(0)); }
现在,每个线程在接触书本之前都要等待锁权限。当我们通过Submit()提交所有内容时,它们将保留并等待。然后,我们用countDown()释放锁,它们才同时开始运行。
查看运行结果:
通过运行结果可以知道,现在线程数还是为10,但是线程的重叠数是大于0的,所以assertTrue执行通过,ids也不等于10了,也就是没有像以前那样得到10个图书ID。显然,Books类不是线程安全的!
在修复优化该类之前,教大家一个简化测试的方法,使用来自Cactoos的RunInThreads,它与我们上面所做的完全一样,但代码是这样的:
@Test publicvoidaddsAndRetrieves5(){ Booksbooks=newBooks(); MatcherAssert.assertThat( t->{ Stringtitle=String.format( "Book#%d",t.getAndIncrement() ); intid=books.add(title); returnbooks.title(id).equals(title); }, newRunsInThreads<>(newAtomicInteger(),10) ); }
assertThat()的第一个参数是Func(一个函数接口)的实例,接受AtomicInteger(RunsThreads的第一个参数)并返回布尔值。此函数将在10个并行线程上执行,使用与上述相同的基于锁的方法。
这个RunInThreads看起来非常紧凑,用起来也很方便,推荐给大家,可以用起来的。只需要在你的项目中添加一个依赖:
org.llorllale cactoos-matchers 0.18
最后,为了使Books类成为线程安全的,我们只需要向其方法add()中同步添加就可以了。或者,聪明的码小伙伴们,你们有更好的方案吗?欢迎留言,大家一起讨论。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。