返回

多线程下的 ConcurrentHashMap 精要解析 02

后端

在 ConcurrentHashMap 的源码分析中,put() 方法是一个重点。它负责将键值对插入哈希表,并在多线程环境下保证线程安全。

public V put(K key, V value) {
    return putVal(key, value, false, true);
}

put() 方法首先调用 putVal() 方法,它是一个私有方法,负责实际的插入操作。putVal() 方法有四个参数:key、value、onlyIfAbsent 和 evict。onlyIfAbsent 表示只有在键不存在的情况下才插入值,evict 表示如果哈希表已满,是否驱逐最老的条目。

在 putVal() 方法中,首先根据 key 计算哈希值,然后根据哈希值找到对应的桶。如果桶不存在,则创建一个新的桶。如果桶已存在,则将键值对插入桶中。

Segment<K,V> segment = (Segment<K,V>)table[hash & (table.length - 1)];

插入操作可能导致桶中的条目数超过阈值。如果超过阈值,则需要对哈希表进行扩容。扩容操作会创建一个新的哈希表,并将旧哈希表中的条目复制到新的哈希表中。

if (segment.count >= threshold) {
    expandTable();
}

在 putVal() 方法的最后,如果插入操作成功,则返回旧值,否则返回 null。

if (segment.put(key, value, hash, onlyIfAbsent, evict))
    return null;
else
    return segment.get();

put() 方法是 ConcurrentHashMap 源码分析的重点方法,这里涉及到并发扩容,桶位寻址等等...JDK1.8 ConcurrentHashMap 的结构图:

1、put(K key, V valu

                                   /--------------\
                                  /                \
                       _________/                  \_________
                      /         \                    /         \
                rootSegmentArray|       tailList |segmentArray|  headList  |
                []             |  []             []             | []
                        ^                 ^
                        |                 |
                 segment 0                segment 1

在 ConcurrentHashMap 中,哈希表被划分为多个段(Segment),每个段由一个 Segment 对象管理。Segment 对象包含一个哈希表,以及一个链表,链表中存储着哈希表中溢出的条目。

当向 ConcurrentHashMap 中插入一个键值对时,首先根据键计算哈希值,然后根据哈希值找到对应的段。如果段中没有找到对应的键,则将键值对插入段中的哈希表。如果段中的哈希表已满,则将键值对插入段中的链表。

当段中的链表长度超过阈值时,段会进行扩容。扩容操作会创建一个新的哈希表,并将链表中的条目复制到新的哈希表中。

ConcurrentHashMap 的 put() 方法是线程安全的。当多个线程同时向 ConcurrentHashMap 中插入键值对时,ConcurrentHashMap 会使用锁来保证线程安全。锁的粒度是段,即每个段都有自己的锁。当一个线程试图修改一个段时,它必须先获得该段的锁。

ConcurrentHashMap 的 put() 方法是高性能的。ConcurrentHashMap 使用分段锁来减少锁竞争,并使用链表来处理哈希表中的溢出条目。这两种技术可以有效地提高 ConcurrentHashMap 的性能。