ConcurrentHashMap原理

152 阅读2分钟

1.7

分段锁 segment

数组 Segment + 数组 HashEntry + 链表 HashEntry结点

image-20210425142532796

对数组进行分割,锁住部分数据

ReentrantLock lock-> Segment

1.8

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations

Node数组 + 链表/红黑树

image-20210425142813886

基本锁原理

CAS + synchronized -> Node

get方法没有加锁 node 里面的 next 和 value 加了 volatile关键字

put方法

计算key的hash值,计算出在数组table的下标i

1. table[i]==null (数组目标位置的node为空)

说明之前这个位置没有元素,可以直接插入,chm采用的是cas比较替换的方法插入

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

ASHIFT :每个node的大小

ABASE :数组开始存值的偏移地址

2. firstNode.hash == MOVED(-1)

表示正在扩容中,可以帮助多线程扩容

tab = helpTransfer(tab, firstNode);

-->  
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
3. 比较插入

synchronized(firstNode)

锁住数组此位置上的第一个结点

如果是链表结点,比较hash and key.equals()

如果是树结点( firstNode.hash == TREEBIN(-2) ), 用红黑树的方法查找和插入,再平衡

resize() core
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    
    
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);  //在旧的table的firstNode设置ForwardingNode 别的线程如果查询时这个结点则帮助扩容
advance = true; //处理下一个结点扩容
    
    
    
//扩容完成的操作
nextTable = null;
table = nextTab;
                  

for循环table的结点,一个个位置扩容直到全部扩容完成

扩容的时间锁住firstNode synchronized(firstNode)

如果扩容完成则加一个标记 table[i]=ForwardingNode

get()方法
 if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }

1. table[i] == key

firstNode为查找的目标,直接返回value

2. table[i].hash == -1

说明老table对应的结点已经扩容完成,在ForwardingNode可以用nextTable

去查找

3. table[i].hash == -2

说明table对应的结点是树结点,用树结点的方式去查找

4. 链表查找

遍历链表查找

优点
  1. 锁粒度降低
  2. 无锁操作,get无锁,volatile特性
  3. 乐观锁替换,cas替换第一个元素
  4. 并发扩容
  5. 使用优化后的synchronized关键字,性能也高