JAVA8 ConcurrentHashMap的源码完全分析

本文详细分析了JAVA8 ConcurrentHashMap的源码,包括其散列计算方法、构造函数、特殊函数、元素插入过程和查找操作。在实现中,ConcurrentHashMap使用了普通链表、转移节点和树节点来管理元素,当链表长度超过8时转换为红黑树。在扩容过程中,每个线程按特定步长转移数据,并通过sizeCtl控制扩容状态。查找元素时,根据桶类型调用不同查找方法。
摘要由CSDN通过智能技术生成

JAVA8 ConcurrentHashMap的源码完全分析


一. 概述

ConcurrentHashMap内部实现中,一个有table列表,列表中的元素指向一个桶(bin),该桶的元素头有以下三种:

  • 普通链表节点:通常是桶中元素小于8个,就是一个单链表,头元素hash > 0
  • 转移节点(MOVED): 表明当前正长扩容中,当前的节点元素已经被转移到新table中,头元素hash = -1
  • 树节点(TREEBIN): 表示当前的桶是一个红黑二叉树桶,头元素hash = -2
  • 占位节点(RESERVED):一般用于当key对应的值缺失需要计算的场景,在计算出新值之前临时占坑位用的,计算出来之后就用普通Node节点替换掉,头元素hash = -3

桶列表Table默认初始大小为n=16,最大为n=2^31,负载阈值为0.75*n,当桶中普通链表的元素数量超过8个就会转成红黑二叉树,当桶中红黑树的元素减少到6个就会转成普通的单链表形式。在扩容的过程中,每个线程转移数据的索引数量步伐为Max(NCPU > 1 ? (n >>> 3) / NCPU : n, 16),最小值为16。

二. 源码分析

2.1 hash的散列计算方法

对于table大小为n的表格,其散列计算方法为((hash^(hash >>> 16))&0X7FFFFFFF) & n,其中n为2的幂值(n = 2^x),源码如下:

// http://www.easysb.cn/?p=325&preview=true
// Jekkay Hu

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
    
...
int h = spread(key.hashCode());
...

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
    

2.2 构造函数

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        this.sizeCtl = cap;
    }

其中sizeCtl的用作表初始化和扩容控制,具体可以参考注释,下面代码也有解释。

    /**
     * Table initialization and resizing control.  When negative, the
     * table is being initialized or resized: -1 for initialization,
     * else -(1 + the number of active resizing threads).  Otherwise,
     * when table is null, holds the initial table size to use upon
     * creation, or 0 for default. After initialization, holds the
     * next element count value upon which to resize the table.
     */
    private transient volatile int sizeCtl;

2.3 有意思的函数

  • 函数tableSizeFor,功能是向上取2的幂的函数,如下

    /**
     * Returns a power of two table size for the given desired capacity.
     * See Hackers Delight, sec 3.2
     */
    private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  • 根据索引获取表对应桶的几个主要方法,使用sun.misc.Unsafe的方法:

// 功能: return tab[i]
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);
}

// 功能: if(tab[i] == c) tab[i] = v
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);
}

// 功能: tab[i] = 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);
}

2.4 元素插入

元素的插入较为复杂,可以直接看源代码的中文注释。

    // 插入数据,putIfAbsent表示只有缺失时才插入,否则强制更新
    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 不允许空值
        if (key == null || value == null) throw new NullPointerException();
        // 计算hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                // 初始化表格
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
      // tabAt原子操作f= tab[i],找到表格中对应的桶,i为hash投影的索引
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                // casTabAt原子操作,主要是tab[i]= new Node(...),如果失败则表示
         
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值