需要简单解释“锁条带化”如何与 ConcurrentHashMap 一起工作

Posted

技术标签:

【中文标题】需要简单解释“锁条带化”如何与 ConcurrentHashMap 一起工作【英文标题】:Need simple explanation how "lock striping" works with ConcurrentHashMap 【发布时间】:2013-04-15 14:53:18 【问题描述】:

根据 Java Concurrency in Practice,第 11.4.3 章说:

锁拆分有时可以扩展到分区 锁定一组可变大小的独立对象,在这种情况下 它被称为锁条带化。例如,执行 ConcurrentHashMap 使用 16 个锁的数组,每个锁守卫 1/16 哈希桶; bucket N 由 lock N mod 16 保护。

我在理解和可视化锁条带化和存储桶机制方面仍然存在问题。 有人可以用很好理解的话来解释这个:)

提前致谢。

【问题讨论】:

【参考方案1】:

哈希映射建立在一个数组上,其中哈希函数将一个对象映射到底层数组中的一个元素。假设底层数组有 1024 个元素 - ConcurrentHashMap 实际上将它变成了 16 个不同的 64 个元素的子数组,例如0, 63, 64, 127 等。每个子数组都有自己的锁,因此修改 0, 63 子数组不会影响 64, 127 子数组 - 一个线程可以写入第一个子数组,而另一个线程写入第二个子数组。

【讨论】:

好的,我明白了。但是,如果两个或更多线程试图修改子数组 0,63 中的所有内容呢? 那么它是先来先服务的——第一个获取锁的线程进行更改,然后当它完成时,第二个线程进行更改。 ConcurrentHashMap 有类似replace 的方法来确保第二个线程不会无意中覆盖第一个线程的更改。 我不认为它实际上是“先到先得”,正如我所理解的(我没有确切的报价,但我从 Java Concurrency in Practice 中了解到,)只有保证公平当它是显式的时,例如在不同显式Lock 实现的构造函数中,例如ReentrantLock,或诸如ArrayBlockingQueue 的队列。 (我知道这是一个旧线程,对不起) 简要了解了 Java 8 的实现,但我没有看到任何段级数组。我知道上面的答案是在 java 8 之前写的。但是现在有人可以澄清段只不过是表中的单个存储桶或行吗?由于我们不对行的条带进行锁定,因此锁条带化这个词已经过时了吗?【参考方案2】:

锁定Collections.synchronizedMap()ConcurrentHashMap 的区别如下:

如果多个线程将频繁访问Collections.synchronizedMap(),将会有很多争用,因为每个方法都使用共享锁同步(即如果线程X调用Collections.synchronizedMap()上的方法,所有其他线程将被阻塞从调用 Collections.synchronizedMap() 上的任何方法直到线程 X 从它调用的方法返回)。

ConcurrentHashMap 具有可变数量的锁(默认为 16),每个锁保护ConcurrentHashMap 中的一段键。所以对于一个有 160 把钥匙的ConcurrentHashMap,每个锁将保护 10 个元素。因此,对键(getputset 等)进行操作的方法仅锁定对键在同一段中的键操作的其他方法的访问。例如,如果线程 X 调用 put(0, someObject),然后线程 Y 调用 put(10, someOtherObject),这些调用可以并发执行,并且线程 Y 不必等待线程 X 从 put(0, someObject) 返回。下面提供了一个示例。

此外,size()isEmpty() 等某些方法根本不受保护。虽然这允许更大的并发性,但这意味着它们不是强一致的(它们不会反映同时发生变化的状态)。

public static void main(String[] args) 
  ConcurrentHashMap<Integer, Object> map = new ConcurrentHashMap<>(160);

  new Thread(new Runnable() 
    @Override
    public void run() 
      map.put(0, "guarded by one lock");
    
  .start();

  new Thread(new Runnable() 
    @Override
    public void run() 
      map.put(10, "guarded by another lock");
    
  .start();

  new Thread(new Runnable() 
    @Override
    public void run() 
      // could print 0, 1, or 2
      System.out.println(map.count());
    
  .start();

【讨论】:

Java 中的 HashMap 不是线程安全的,HashTable 是。你说的第一个场景是 HashTable【参考方案3】:

这里的关键概念是“桶”。而是为整个哈希表使用全局锁,它为每个存储桶使用一个小锁。 这也是一个很好的类比桶排序,可以提高排序的复杂性。

【讨论】:

以上是关于需要简单解释“锁条带化”如何与 ConcurrentHashMap 一起工作的主要内容,如果未能解决你的问题,请参考以下文章

序列化与反序列化的简单粗暴解释

什么是文件描述符,用简单的术语解释?

如何用简单的英语解释回调?它们与从另一个函数调用一个函数有何不同?

关于管道如何在 Bash 中工作的简单解释是啥?

ConcurrentHashMap

ConcurrentHashMap