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

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

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

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

提前致谢。

【问题讨论】:

    标签: java concurrency java.util.concurrent concurrenthashmap


    【解决方案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】:

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

    【讨论】:

      猜你喜欢
      • 2019-12-05
      • 1970-01-01
      • 1970-01-01
      • 2016-08-10
      • 2017-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多