为什么ConcurrentHashMap不能锁定每个桶?

Mia*_*ach 16 java concurrency multithreading concurrenthashmap

我们知道,java的ConcurrentHashMap有许多内部锁,每个内部锁保护桶数组的某些区域.

一个问题是:为什么我们不能为每个桶创建一个锁

已经提出了类似问题:Java ConcurrentHashMap中增加分区数量的缺点?

根据答案,有几个原因:

  1. 同时运行的最大线程数受处理器核心数量的限制.它是否正确? 我们是否总是声明如果我们有8核处理器,我们在ConcurrentHashMap中不需要超过8个锁定区域?

  2. 浪费L2缓存.为什么?

  3. 浪费了记忆.看起来这是因为额外的锁创建.

还有其他原因吗?

Pat*_*ity 5

希望我做得很好解释......此刻匆匆忙忙......

你的第一个问题的答案:

"为什么我们不能为每个桶创造一个锁?"

是你可以为每个桶创建一个锁 - 它不一定是最好的行动方案.

你的问题的答案:

"我们总是可以说,如果我们有8核处理器,我们在ConcurrentHashMap中不需要超过8个锁定区域"

在技​​术上是"不",但它取决于你的"需要"是什么意思.拥有一些与您的系统的最大并发性相匹配或稍微大一些的区域并不一定能阻止争用,但在实践中它的效果非常好.即使有其他区域没有被锁定,也没有什么能阻止两个线程同时尝试访问同一个区域.

可以通过具有8核心处理器上的8个区域或更多保证的是,各地区可同时无争进行访问.如果您有8个内核(不是超线程),则可以同时执行最多8个操作.即使这样,理想数量的区域可能比核心数量更多(比如16),因为它会以较低的成本(仅8个额外的锁定)使争用不太可能发生.

随着区域数量相对于最大并发性的增加,附加区域的好处最终会减少,这会导致它们浪费空间(内存),如JavaDoc中所述.它是争用可能性之间的平衡(给定一个区域的锁定,另一个线程将尝试访问它的概率是多少)和浪费的空间.

还有一些其他因素会影响a的表现ConcurrentHashMap:

  • 锁定代码的执行时间 - 最好将锁定的代码段缩小,以便快速完成并释放锁.释放锁越快,解决争用的速度就越快.
  • 数据分布 - 在高并发性下,分布良好的数据往往表现更好.将所有数据集中在一个区域内意味着您将始终遇到争用.
  • 数据访问模式 - 同时访问不同的数据区域将表现得更好,因为您的线程不会争用资源锁.如果您一次只尝试访问一个区域,那么具有良好分布的数据并不重要.

无论有多少个区域,这三个区域都可以对性能产生积极或消极的影响,并且可以使区域数量不那么重要.由于它们起着重要作用,因此它们使得拥有更多区域的可能性降低.由于您只能同时执行这么多线程,因此拥有快速完成工作并释放锁定的线程是一个更好的关注点.

至于你关于缓存的问题:老实说我不确定,但我可以猜一猜.当您大量使用地图时,这些锁将最终停留在缓存上并占用空间,可能会突破其他可能更有用的东西.缓存比主内存更加稀缺,缓存丢失浪费了大量时间.我认为这里的想法是将大量事物放在缓存上而不提供显着优势的普遍厌恶.采取极端:如果缓存充满了锁(以某种方式)并且每个数据调用都发送到内存,那么性能就会受到影响.


Hol*_*ger 4

\n

我们是否可以始终声明,如果我们有 8 核处理器,则 ConcurrentHashMap 中不需要超过 8 个锁定区域?

\n
\n\n

不,这是完全错误的。它取决于两个因素,线程数(并发)和段冲突数。如果两个线程竞争同一段,一个线程可能会阻塞另一个线程。

\n\n

虽然拥有核心的线程数只能与核心数一样多,但上述语句的最大错误是假设不在核心上运行的线程不能拥有锁。但是,拥有锁的线程仍然可能会在下一个线程的任务切换时释放 CPU,然后在尝试获取相同的锁时会被阻塞。

\n\n

但是将线程数调整为核心数并不罕见,特别是对于计算密集型任务。因此,a 的并发级别ConcurrentHashMap间接取决于典型设置中的核心数量。

\n\n
\n\n

为每个桶拥有一个锁意味着为每个桶维护一个锁状态和一个等待队列,这意味着需要相当多的资源。请记住,仅并发写入操作需要锁,而读取线程不需要锁。

\n\n

然而,对于 Java\xc2\xa08 实现来说,这种考虑已经过时了。它使用无等待算法来更新存储桶,至少对于没有冲突的存储桶来说是这样。这有点像每个桶都有一个锁,因为在不同桶上操作的线程不会互相干扰,但没有维护锁状态和等待队列的开销。唯一需要关心的是给地图一个合适的初始大小。因此,concurrencyLevel如果指定,则将用作初始大小调整提示,否则将被忽略。

\n