[英]Why ConcurrentHashMap cannot have a lock for each bucket?
我们知道,java 的 ConcurrentHashMap 有许多内部锁,每个锁都保护着存储桶数组的某个区域。
一个问题是:为什么我们不能为每个桶创建一个锁?
已经问过一个类似的问题: Java ConcurrentHashMap 中增加分区数量的缺点?
根据回答,有以下几个原因:
同时运行的最大线程数受处理器内核数的限制。 这样对吗? 我们是否可以始终声明,如果我们有 8 核处理器,我们在 ConcurrentHashMap 中不需要超过 8 个锁定区域?
浪费了 L2 缓存。 为什么?
存在内存浪费。 看起来这是因为额外的锁创建。
还有其他原因吗?
希望我能很好地解释……此刻有点匆忙……
你的第一个问题的答案:
“为什么我们不能为每个存储桶创建一个锁?”
是您可以为每个存储桶创建一个锁——这不一定是最好的行动方案。
你的问题的答案:
“我们能否始终声明,如果我们有 8 核处理器,我们不需要在 ConcurrentHashMap 中超过 8 个锁定区域”
从技术上讲是“否”,尽管这取决于您所说的“需要”是什么意思。 拥有多个与您的系统的最大并发数相匹配或稍大的区域不一定能防止争用,但在实践中它工作得很好。 没有什么可以阻止两个线程同时尝试访问同一个区域,即使还有其他区域没有被锁定。
通过在 8 核处理器上拥有 8 个或更多区域,您可以保证可以同时访问所有区域而不会发生争用。 如果您有 8 个内核(不是超线程),您最多可以同时执行 8 个操作。 即便如此,理想的区域数量(例如 16 个)也可能比核心数量多,因为它会以较低的成本(仅 8 个额外的锁)减少争用的可能性。
正如JavaDoc 中提到的那样,随着区域数量相对于最大并发数的增加,拥有额外区域的好处最终会减少,这导致它们浪费空间(内存)。 这是争用可能性(给定一个区域上的锁,另一个线程尝试访问它的可能性有多大)和浪费空间之间的平衡。
还有一些其他因素会影响ConcurrentHashMap
性能:
无论有多少个区域,所有这三件事都会对性能产生积极或消极的影响,并可能降低区域数量的相关性。 由于它们发挥着重要作用,因此它们使拥有更多区域的总体上对您有所帮助的可能性降低。 由于您只能同时执行这么多线程,因此拥有快速完成工作并释放锁的线程是更好的关注点。
至于您关于缓存的问题:老实说,我不确定,但我可以猜测一下。 当您大量使用地图时,这些锁最终会出现在缓存上并占用空间,可能会破坏其他可能更有用的东西。 缓存比主存稀缺得多,缓存未命中会浪费大量时间。 我认为这里的想法是普遍厌恶将很多东西放在缓存中,而这些东西不会带来显着的好处。 极端情况:如果缓存中充满了锁(不知何故)并且每个数据调用都传到内存中,那么您的性能就会受到影响。
我们是否可以始终声明,如果我们有 8 核处理器,我们在 ConcurrentHashMap 中不需要超过 8 个锁定区域?
不,这是完全错误的。 它取决于两个因素,线程数(并发)和段冲突数。 如果两个线程竞争同一段,一个线程可能会阻塞另一个。
虽然拥有内核的线程数只能与内核数一样多,但上述语句的一个大错误是假设不在内核上运行的线程不能拥有锁。 但是拥有锁的线程仍然可以在下一个线程的任务切换上释放 CPU,然后在尝试获取相同的锁时被阻塞。
但是根据核心数调整线程数并不罕见,尤其是对于计算密集型任务。 因此ConcurrentHashMap
的并发级别间接取决于典型设置中的内核数量。
每个桶都有一个锁意味着为每个桶维护一个锁状态和一个等待队列,这意味着相当多的资源。 请记住,只有并发写操作需要锁,读线程不需要。
但是,对于 Java 8 实现,这种考虑已经过时。 它使用无等待算法进行桶更新,至少对于没有冲突的桶是这样。 这有点像每个桶都有一个锁,因为在不同桶上运行的线程不会相互干扰,但没有维护锁状态和等待队列的开销。 唯一需要关心的是给地图一个合适的初始大小。 因此,如果指定了concurrencyLevel
,则将用作初始大小调整提示,否则将被忽略。
Java 8 的ConcurrentHashMap确实在每个存储桶上加锁。 写入锁定,但可能发生并发读取。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.