ConcurrentHashMap扩容原理

本文探讨了JDK8中ConcurrentHashMap的实现原理,它采用Node数组+链表+红黑树结构,基于CAS算法实现线程安全。在put操作时,当达到特定阀值,会进行扩容,新table容量为原table的2倍。在扩容过程中,通过(n-1)&hash确定元素在新table中的位置,保证下标相对长度不变,提高并发性能。此外,文章还分析了链表转红黑树的条件以及在多线程环境下的状态管理。
摘要由CSDN通过智能技术生成

前言

         ConcurrentHashMap从名称是可以看出,它是一个HashMap而且是线程安全的。在多线程编程中使用非常广泛。ConcurrentHashMap的实现方式,在jdk6,7,8中都不一样。本文只针对jdk8中的实现作一些说明。

ConcurrentHashMap实现原理

        先来看看ConcurrentHashMap底层是发何实现的。总的来说,它是采用Node<K,V>类型(继承了Map.Entry)的数组table+单向链表+红黑树的结构。table数组的大小默认为16,数组中的每一项称为桶(bucket),桶中存放的是链表或者是红黑树结构,取决于链表的长度是否达到了阀值8(大于等于8)(默认),如果是,接着再判断数组的长度是否小于64,如果小于则优先扩容table容量来解决单个桶中元素增多的问题,如果不是则转换成红黑树结构存放。

再次,我们看到ConcurrentHashMap类中,Unsafe类。说明线程安全的实现是基于CAS算法的无锁化修改值的操作,它可以大大降低锁带来的性能消耗。其基本思想是不停的去比较当前内存中的变量值与给定的值是否相同(值相等且引用也相等),如果相同则修改成指定的值,否则什么也不做。这与乐观锁的思想类似。缺点就是消耗CPU性能。

private static final sun.misc.Unsafe U;
U = sun.misc.Unsafe.getUnsafe();

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

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);
}

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

 

源码分析

        先来看看ConcurrentHashMap扩容是如何发生的,主要是在put一个KV时,如果达到某些阀值则会重新new一个nextTable其长度是原table的2倍。

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

/** Implementation for put and putIfAbsent */
//onlyIfAbsent的意思是在put一个KV时,如果K已经存在什么也不做则返回null
//如果不存在则put操作后返回V值
final V putVal(K key, V value, boolean onlyIfAbsent) {
    //ConcurrentHashMap中是不能有空K或空V的
    if (key == null || value == null) throw new NullPointerException();
    //hash算法得到hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //如果table是空的,就去初始化,下一个循环就不是空的了
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
            //如果没有取到值,即取i位的元素是空的,为什么i取值是(n-1)&hash??
            //这是hash的精华所在,在这里可以先思考一下
            //此时直接到KV包装成Node节点放在i位置即可
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //MOVED,定义为-1。标记原table正在执行扩容任务,可以去帮忙(支持多线程扩容)
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            //这种情况是,在i的位置找到了一个元素,说明此元素的K与之间的某个K的hash结果是一样的
            //
            V oldVal = null;
            synchronized (f) {//同步锁住第一个元素
                if (tabAt(tab, i) == f) {//为了安全起见,再一次判断
                    if (fh >= 0) {//节点的hash值大于0,说明是一个链表结构
                        binCount = 1;//记录链表的元素个数
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //判断给定的key是否与取出的key相同,如果是则替换元素
                            if (e.hash == hash &&
                                ((ek &#
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值