1.是什么
ConcurrentHashMap
是 Java 并发包(java.util.concurrent)中的一个线程安全的哈希表实现。与 HashMap
相比,ConcurrentHashMap
在并发环境下具有更高的性能,因为它允许多个线程并发地进行读写操作而不会导致数据不一致。
以下是 ConcurrentHashMap
实现的一些关键点:
分段锁(Segment Locking)
ConcurrentHashMap
内部使用了分段锁(Segment Locking)技术,将整个哈希表分割成若干个小的部分,称为段(Segment)。每个段其实就是一个小的哈希表,它们有自己的锁。当对哈希表进行修改时,只需要锁定相应的段,而不需要锁定整个哈希表。这大大减少了锁的竞争,提高了并发性能。
线程安全的操作
ConcurrentHashMap
提供了以下线程安全的操作:
- get() 方法:由于
get()
方法不会修改表中的任何数据,所以它是完全无锁的,通过volatile读和Unsafe操作来确保读取的数据是最新的。 - put() 方法:当向
ConcurrentHashMap
中插入数据时,会根据键的哈希值定位到对应的段,并对该段加锁。只有对应的段被锁定,其他段不受影响。 - size() 方法:计算
ConcurrentHashMap
的大小需要考虑所有段的大小。ConcurrentHashMap
通过尝试多次计算所有段的大小,并在两次尝试之间对所有的段进行加锁,以确保计算的准确性。
实现细节
以下是 ConcurrentHashMap
的一些实现细节:
- 段(Segment):
ConcurrentHashMap
包含一个Segment
数组,每个Segment
维护一个哈希表。 - 哈希表(Hash Entry):每个
Segment
内部包含一个HashEntry
数组,用于存储键值对。 - 锁(ReentrantLock):每个
Segment
对象都有一个ReentrantLock
,用于保护其内部的数据结构。
示例解释
假设我们要在 ConcurrentHashMap
中插入一个键值对(key, value):
- 计算键(key)的哈希值,确定它应该落在哪个段(Segment)。
- 获取对应段的锁。
- 在该段内进行插入操作,如果存在键(key)的冲突,会形成链表。
- 插入完成后,释放段的锁。
以下是伪代码示例:
public V put(K key, V value) {
int hash = hash(key);
Segment<K,V> segment = getSegmentFor(hash);
return segment.put(key, hash, value, false);
}
// Segment 内部的 put 方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock(); // 对当前段加锁
try {
// ... 在此处执行插入逻辑 ...
} finally {
unlock(); // 释放锁
}
}
总结
ConcurrentHashMap
通过分段锁技术,减少了锁的粒度,提高了并发访问的性能。它的设计允许并发读和一定程度的并发写,而不会引起线程安全问题。这些特性使得 ConcurrentHashMap
成为处理并发数据结构时的一个重要选择。