无法加载可扩展计数器
到处都需要计数器,例如,查找应用程序的关键KPI,应用程序的负载,服务的请求总数,用于查找应用程序吞吐量的一些KPI等。
由于所有这些需求,并发复杂性也增加了,这使这个问题变得有趣。
如何实现并发计数器
- 同步 –这是JDK 1.5之前的唯一选项,因为现在我们正在等待JDK8发布,因此绝对不是一个选项。
- 基于锁 –切勿尝试将其用作计数器,否则它将性能很差
- 等待免费 -Java不支持Fetch-and-add ,因此实现起来有点困难。
- 无锁 –在“ 比较和交换”的良好支持下,此选项看起来不错。
基于比较和交换的计数器如何执行
我使用AtomicInteger进行此测试,并且每个线程使此计数器递增1百万次,以增加线程的争用数量,并逐渐增加。
试验机详细信息
作业系统:Windows 8
JDK:1.7.0.25
处理器:Intel i7-3632QM,8 Core
内存:8 GB
Y轴 –增加一百万次所需的时间
X轴–螺纹数
随着线程数量的增加,增加计数器所花费的时间也增加了,这是由于争用造成的。 对于基于CAS的计数器,是CAS故障导致减速。
这是我们可以获得的最佳性能吗? 肯定不是它们是实现并发计数器的更好的解决方案,让我们来看看它们。
备用并发计数器
让我们看一些实现计数器的解决方案,以更好的方式处理竞争:
- 基于核心的计数器 –维护每个逻辑核心的计数器,这样您将减少争用。 您拥有这种类型的计数器的唯一问题是,如果线程数大于逻辑核心数,那么您将开始注意到竞争。
- 基于线程的计数器 –维护计数器以了解将要使用系统的线程总数。 当线程数大于逻辑核心数时,此方法效果很好。
让我们测试一下
不同类型的计数器花费的时间
Y轴 –增加一百万次所需的时间
X轴–螺纹数
同时用计数器进行比基于原子计数器要好得多,16个线程是5倍左右的时间比较好,这是巨大的差异!
CAS失效率
Y轴 – CAS失效100Ks
X轴–螺纹数
由于存在争用,基于原子的计数器会出现很多故障,并且随着我添加更多线程和其他计数器的运行而呈指数级增长。
观察
多核计算机变得易于使用,并且我们必须改变处理并发的方式,而当拥有24或48核服务器的情况很普遍时,传统的并发方式在当今时代将无法扩展。
- 为了减少争用,您必须使用多个计数器,然后再将它们聚合
- 如果线程数少于或等于内核数,则基于内核的计数器效果很好
- 当线程数量远大于可用内核时,基于线程的计数器就很好
- 减少争用的关键是确定要写入哪个线程的计数器。我使用了基于线程ID的简单方法,但是可以使用更好的方法,请查看JDK 8的ThreadLocalRandom以获得一些想法。
- JDK8的LongAdder使用基于线程的方法,该方法创建许多插槽以减少争用。
该测试中使用的所有计数器的代码都可以在Github上获得
翻译自: https://www.javacodegeeks.com/2013/09/scalable-counters-for-multi-core.html
无法加载可扩展计数器