标签:atom shm read xor 发展 4类 单元 end 对象
Striped64是java1.8 juca中新增的多个计数器类的基础类。它的基本思想其实与并发数据结构的发展息息相关:
当然具体情况还要具体分析,阻塞锁,无锁(CAS,自旋锁),避免线程争用(单元个数)都需要在考虑实际的情况下使用,比如:
具体的设计思想可以参考其注释:
这个类维护一个懒惰初始化的原子更新变量的表,外加一个额外的“base”字段。table的大小是2的幂。
索引使用掩码下的每个线程的hash code。这个类中几乎所有的声明都是包私有的,由子类直接访问。
表项是Cell类,AtomicLong填充(通过@sun.misc.Contended)的一个变体,以减少缓存争用。
对于大多数原子类来说,填充是多余的,因为它们通常不规则地分散在内存中,因此不会相互干扰。
但是驻留在数组中的原子对象将倾向于彼此相邻放置,因此在没有这种预防措施的情况下,最常见的
情况是共享缓存行(具有巨大的负面性能影响)。
部分原因是因为Cells相对较大,所以我们避免创建Cells,直到需要它们。当没有争用时,
将对base字段进行所有更新。第一次争用时(base更新时CAS失败),该表被初始化为大小2。
在进一步竞争时,表的大小加倍,直到达到大于或等于CPU数量的2的最近幂。表插槽保持
为空(null),直到需要为止。
单个自旋锁(“cellsBusy”)用于初始化表,调整其大小,以及用新的Cells填充插槽。
不需要阻塞锁;当锁不可用时,线程会尝试其他插槽(或base)。在这些重试过程中,竞争加剧,
局部性降低,但这仍然优于替代方案。
通过ThreadLocalRandom维护的Thread.probe字段用作每个线程的哈希代码。我们让它们保持未初始化(为零)
(如果它们以这种方式出现),直到它们在插槽0竞争。然后将它们初始化为通常不会与其他值冲突的值。
执行更新操作时,竞争和/或表冲突由失败的情况指示。发生冲突时,如果表的大小小于容量,
那么它的大小将加倍,除非其他线程持有锁。如果散列槽为空,并且锁可用,则会创建一个新的Cells。
否则,如果插槽存在,将尝试CAS。重试通过“双散列”进行,使用二级散列(Marsaglia XorShift)
来尝试找到空闲插槽。
表的大小是有上限的,因为当线程多于CPU时,假设每个线程都绑定到一个CPU,就会有一个完美的
散列函数将线程映射到插槽,从而消除冲突。当我们达到容量时,我们通过随机改变冲突线程的哈希代码
来搜索这个映射。因为搜索是随机的,冲突只有通过CAS失败才知道,收敛可能会很慢,而且因为线程通常
不会永远绑定到CPU,所以根本不会发生。然而,尽管有这些限制,在这些情况下观察到的竞争率通常很低。
当一个Cell的散列到它的线程一旦终止时,以及在表大小加倍导致没有线程在扩展掩码下散列到它的情况下,
该Cell可能会变成未使用。我们不会试图检测或移除这些Cell,因为考虑对于长期运行的情况,观察到的
竞争级别会再次出现,因此最终会再次需要这些Cell。短时间的未使用,并不重要。
标签:atom shm read xor 发展 4类 单元 end 对象
原文地址:https://www.cnblogs.com/redreampt/p/9445540.html