正如我们所知,java的ConcurrentHashMap有许多内部锁,每个锁都保护桶数组的某些区域。
问题是:为什么我们不能为每个存储桶创建一个锁?
已经问过类似问题:Disadvantage of increasing number of partition in Java ConcurrentHashMap?
根据答案,有几个原因:
同时运行的最大线程数受处理器核心数量的限制。 这是正确的吗? 我们是否可以说,如果我们有8核处理器,我们在ConcurrentHashMap中不需要超过8个锁定区域?
浪费L2缓存。的为什么吗
浪费记忆力。看起来这是因为额外的锁创建。
还有其他原因吗?
答案 0 :(得分:5)
希望我做得很好解释......此刻有点匆匆......
第一个问题的答案:
“为什么我们不能为每个桶创建一个锁?”
你可以为每个桶创建一个锁 - 它不一定是最好的行动方案。
你的问题的答案:
“我们是否可以说,如果我们有8核处理器,我们在ConcurrentHashMap中不需要超过8个锁定区域”
在技术上是“否”,但它取决于你的“需要”是什么意思。拥有一些与您的系统的最大并发性相匹配或略微更大的区域并不一定能阻止争用,但在实践中它的效果非常好。即使有其他区域没有被锁定,也没有什么能阻止两个线程同时尝试访问同一个区域。
在<8>处理器上拥有8个或更多区域的可以保证的是,所有区域都可以同时访问而不会发生争用。如果您有8个内核(不是超线程),则可以同时执行最多8个操作。即使这样,理想的区域数量可能比核心数量更多(比如16),因为它会以较低的成本(仅增加8个锁定)来减少争用。
随着区域数量相对于最大并发性的增加,其他区域的好处最终会减少,这会导致它们浪费空间(内存),如JavaDoc中所述。这是争用可能性之间的平衡(给定一个区域的锁定,另一个线程将尝试访问它的概率是多少)和浪费的空间。
还有一些其他因素会影响ConcurrentHashMap
的效果:
无论有多少个地区,这三个地方都会对绩效产生积极或消极的影响,并且可能会使地区数量变得不那么重要。由于它们起着重要作用,因此它们使得拥有更多区域的可能性降低。由于您只能同时执行这么多线程,因此拥有快速完成工作并释放锁定的线程是一个更好的关注点。
关于缓存的问题:老实说我不确定,但我可以猜一猜。当你大量使用地图时,这些锁将最终放在缓存上并占用空间,可能会破坏其他可能更有用的东西。缓存比主内存更加稀缺,缓存丢失浪费了大量时间。我认为这里的想法是将大量事情放在缓存上而不提供显着优势的普遍厌恶。极端地说:如果缓存中充满了锁(以某种方式),并且每个数据调用都会输出到内存中,那么性能就会受到影响。
答案 1 :(得分:4)
我们是否总是声明如果我们有8核处理器,我们在ConcurrentHashMap中不需要超过8个锁定区域?
不,这是完全错误的。它取决于两个因素,线程数(并发)和段冲突的数量。如果两个线程竞争同一个段,则一个线程可能会阻塞另一个线程。
虽然你拥有核心只有拥有核心的线程,但上述语句的最大错误是假设没有在核心上运行的线程不能拥有锁。但是拥有锁的线程仍然可以在下一个线程的任务开关上松开CPU,然后在尝试获取相同的锁时被阻塞。
但是将线程数调整为核心数并不罕见,特别是对于计算密集型任务。因此ConcurrentHashMap
的并发级别间接取决于典型设置中的核心数量。
对每个存储桶执行锁定意味着维护每个存储桶的锁定状态和等待队列,这意味着相当多的资源。请记住,只有并发写操作才需要锁,但读取线程不需要。
但是,对于Java 8实现,这种考虑已经过时。它使用等待算法进行存储桶更新,至少对于没有冲突的存储桶。这有点像每个桶有一个锁,因为在不同桶上运行的线程不会相互干扰,但没有维持锁定状态的开销。等待队列。唯一要关心的是给地图一个合适的初始大小。因此,concurrencyLevel
(如果已指定)将用作初始大小提示,但会被忽略。
答案 2 :(得分:0)
Java 8的ConcurrentHashMap确实在每个存储桶上都设置了锁。可以锁定写入,但可以同时进行读取。