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 线程安全性

java 线程安全性_我如何测试Java类的线程安全性相关推荐

  1. springboot tomcat默认线程数_记一次JAVA线程池的错误用法

    最近项目一个项目要结项了,但客户要求 TPS 能达到上千,而用我写的代码再怎么弄成只能达到 30 + 的 TPS,然后我又将代码中能缓存的都缓存了,能拆分的也都拆分了,拆分时用的线程池来实现的:其实现 ...

  2. java arraylist线程安全_面试题1:ArrayList 是线程安全的吗?如果要实现一个线程安全的List应该怎么做?...

    ZJ面试被问到的问题,我们来一个一个问题看 首先第一个问题,ArrayList是线程安全的吗? 答案是不是,我们可以看看ArrayList的源代码 public E set(int index, E ...

  3. java线程堆栈_深入JVM剖析Java的线程堆栈

    在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象 ...

  4. java 线程钩子_高级并发编程系列六(线程池钩子函数)

    1.考考你 国庆假期快要结束了,准备回到工作岗位的你,是不是已经开始撸起袖子敲代码,反正发完文章我就要准备去加班了,程序员就这样,有干劲对吧 那么来吧,让我们一起分享完高级并发编程系列中,线程池小节的 ...

  5. JAVA线程池_并发队列工作笔记0002---认识线程池_在线程池中使用队列

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 上面是线程的执行周期 这个线程的生命周期,可以看到时间都浪费在了创建和销毁的这里了. 实际上执行业 ...

  6. java主线程和子线程区别_主线程异常– Java

    java主线程和子线程区别 Being a Java Programmer, you must have seen exception in thread main sometimes while r ...

  7. java 进程 线程数量_如何查询一个进程下面的线程数(进程和线程区别)

    在平时工作中,经常会听到应用程序的进程和线程的概念,那么它们两个之间究竟有什么关系或不同呢? 一.对比进程和线程 1)两者概念 -  进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程 ...

  8. call线程起名字_高级分享:Java多线程你真的理解透彻了吗?带你玩转一次多线程!...

    不知道怎么引入正文 相信后端同学在开发的时候多多少少都会涉及到多线程开发,作为Java开发的我也同样会经常用到多线程开发. 我认为Java语言在处理多线程上是非常优秀的,我们可以使用简明的代码实现线程 ...

  9. java重新执行_(转载)java线程 - 线程唤醒后并被执行时,是在上次阻塞的代码行重新往下执行,而不是从头开始执行...

    今天重新把昨晚的线程同步面试题做一遍时,发现实际情况运行下来时,线程一直不同步.后来经过不断测试,发现自己的一个误区. 之前一直以为,线程如果被唤醒后再次执行时,会从头开始运行这个线程,也就是重新运行 ...

最新文章

  1. 自动化早已不是那个自动化
  2. python爬虫新手项目-Python爬虫实战之取电影天堂,,新手练手项目
  3. VTK修炼之道70:体绘制讨论_光照阴影、VTKLODProp3D
  4. C#怎么给新建的winform程序添加资源文件夹Resources
  5. 新唐c语言怎么计算指数运算,C语言位域精解
  6. 动态切换父元素隐藏和显示里面的子元素的动画会再一次执行吗?
  7. nssl1458-HR 的疑惑【枚举】
  8. React-Native 填坑之ListView(item更新)
  9. java从键盘输入一个数,并将其倒序输出
  10. 程序猿的骄傲,以及骄傲背后真实的原因
  11. 从宏观的实现原理和设计本质入手,带你理解 AOP 框架的原理
  12. RHEL5 基于虚拟用户验证的VSFTP服务器
  13. 重磅!Google ARCore 和京东 AR 联合举办消费应用创新大赛
  14. Python之txt数据导入
  15. 30种已验证有效的WordPress博客网上赚钱方法
  16. 微信红包随机算法转载
  17. 【办公必备软件】万彩办公大师教程丨PDF转HTML工具
  18. js层级轮播图兼容IE8及以上浏览器
  19. 机器学习中的编码器-解码器结构哲学
  20. 国内各IE内核浏览器所调用的IE版本--转了

热门文章

  1. P3244-[HNOI2015]落忆枫音【dp】
  2. jzoj1402-偷懒的小X【贪心】
  3. 【2018.4.7】模拟赛之五-ssl2386 序列【dp】
  4. Codeforces Global Round 11——E随机+线性基待补
  5. Another Blog
  6. 【模拟】生日蛋糕(jzoj 1613)
  7. Hadoop入门(十五)Mapreduce的数据排序程序
  8. SpringMVC表单验证器的使用
  9. 神奇,教你用随机数打印hello world
  10. python爬虫进阶(初始)