需要简单解释“锁条带化”如何与 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 个元素。因此,对键(get
、put
、set
等)进行操作的方法仅锁定对键在同一段中的键操作的其他方法的访问。例如,如果线程 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 一起工作的主要内容,如果未能解决你的问题,请参考以下文章