[ConcurrentHashMap] 底层原理详解(JAVA 18)

本文详细探讨了Java 18中ConcurrentHashMap的实现细节,包括putVal、initTable、treeifyBin、addCount方法的工作原理。在put操作时,会根据sizeCtl进行初始化或扩容。addCount方法利用CounterCell和baseCount进行并发计数,并在必要时进行扩容。get方法则展示了无锁的查找过程,通过传播哈希和遍历链表或红黑树来获取键值对。文章深入剖析了ConcurrentHashMap如何在高并发下保证性能和线程安全。
摘要由CSDN通过智能技术生成

如果不使用自定义参数创建一个ConcurrentHashMap,调用默认方法 

那么会在put元素时,创建一个大小为16(默认大小)的ConcurrentHashMap。

put方法:

put方法直接调用putVal方法。

我们首先需要知道几个变量的含义:

sizeCtl

-1:代表正在初始化。

负数但不是-1:正在扩容。

未初始化以前,保存table的初始化大小或者0。

初始化以后,保存扩容阈值。

MOVED

static final int MOVED     = -1; // hash for forwarding nodes

MOVED,int常量,值为-1。

putVal

final V putVal(K key, V value, boolean onlyIfAbsent) {
    #首先,判断key和value是否为空,concurrentHashMap不允许null的key或者value。
    if (key == null || value == null) throw new NullPointerException();
    #计算hash值。下文有讲spread方法。
    int hash = spread(key.hashCode());
    #这个用来保存table数组在index(根据key计算出的下标)处的元素个数。
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        #然后我们判断table是否为空,如果为空(创建后第一次put数据),我们就会调用initTable方法进行初始化。
        #下文中有介绍initTable方法。
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        #如果数组不为空,那么我们计算下标(n-1)& hash ,然后判断数组在该位置是否有值。
        #如果为空,那么使用key和value创建node对象,然后使用CAS将对象存放在这个位置。
        #如果成功了,就跳出循环。否则我们需要继续循环。
        #下一次,很有可能下标处头节点已经不为空,如果为空,就继续进行这一步骤。
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
        #判断头结点的hash值是否为MOVED。
        #如果为MOVED,说明concurrenthashmap正在扩容。那么我们调用helpTransfer方法来帮助扩容。
        #同时将tab赋值为扩容后的table。所以,如果插入元素时,concurrenthashmap正在扩容,并且该位置的元素已经
        #被转移到了新的concurrenthashmap,那么线程会先帮助concurrenthashmap进行扩容,扩容后再进行put操作。
        #我们下面会讲到helpTransfer方法。
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        #不讲onlyIfAbsent,基本不会用到。
        else if (onlyIfAbsent // check first node without acquiring lock
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        #如果已经初始化过了,同时也不在扩容状态,那么我们就开始做更新/插入操作。
        #因为扩容是从后往前,逐步扩容,有可能concurrenthashmap正在扩容,但是还没有扩容到index处,那么我们就知道
        #如果扩容时,发现index处正在做插入操作,那么扩容会阻塞,等待插入完成。
        else {
            V oldVal = null;
            #这里用synchronized获取f(头节点)的锁。
            synchronized (f) {
            #获取后再判断一次table[index]处是否还是之前的头对象。
            #如果不是,继续循环(binCount为0,不会跳出循环。看if块下面的代码),
            #否则,开始更新或者添加元素。如果头结点的hash>=0,那么table[index]处存放的是一个链表,我们只需要遍历这个链表就可以了。
        
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    #如果头结点类型为红黑树,那么向红黑树中更新或者添加节点。
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            #binCount代表index处的元素数量,!=0说明我们put成功。
            if (binCount != 0) {
            #扩容完成后,我们根据节点数进行判断,如果大于等于树化阈值,那么转为红黑树。那么是不是>=8就会转成红黑树呢?
            #答案是不一定,我们需要看treeifyBin方法,后面有讲这个方法。
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                #注意,这里如果过oldVal不为空,说明我们做的是一次更新操作,那么就直接返回,不需要调用addCount方法
                #addCount方法用于维护map中元素的总数
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    #如果是一次插入操作,那么可能要对map的node总数加1,然后可能要进行扩容,具体逻辑在addCount方法中,我们下面会讲到。
    addCount(1L, binCount);
    return null;
}

spread这个方法

非常简单,将hash

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值