ConcurrentHashMap源码学习基于JDK1.8

结构

与 HashMap 类似,使用数组 + 链表 + 红黑树存储键值对

成员变量

transient volatile Node<K,V>[] table; // 存放 Node,第一次插入数据时候进行初始化,长度为 2 的倍数

private transient volatile Node<K,V>[] nextTable; // 仅扩容时用到,将节点迁移到新数组

private static final int MIN_TRANSFER_STRIDE = 16 // 扩容线程每次最少要迁移16 个 hash 桶,在扩容中,参与的单个线程允许处理的最少 table 桶首节点个数,虽然适当添加线程,会使得整个扩容过程变快,但需要考虑多线程内存同时分配的问题

private transient volatile int sizeCtl;

  • 默认为 0

  • -1 表示正在初始化

  • -(1+number) 表示有 number 个线程同时在扩容,线程必须竞争到这个共享变量,才能进行初始化或者扩容。

  • 正数, table 中元素数量阈值,超过这个阈值就会扩容
    在这里插入图片描述
    static final int MOVED = -1; // ForwardingNode 的 hash 值,为 -1

    • ForwardingNode,在扩容时使用,如果 index 处 Node 节点 hash 值为 -1,表示正在扩容。

static final int TREEBIN = -2; // 树节点的 Hash 值

static final int RESERVED = -3; // 临时保留的 Hash 值

static final int HASH_BITS = 0x7fffffff; // usable bits of normal

哈希桶 Table 初始化

  • 根据共享变量 sizeCtl 的值来决定是否由当前线程执行初始化操作(单线程进行初始化),若 sizeCtl < 0 表示正在扩容,该线程需要等待。

  • sizeCtl 为正数时,表示 table 的阈值(= 0.75*n),元素个数超过这个值将会扩容。

private final Node<K,V>[] initTable() {
   
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
   
    	// 如果共享变量 sizeCtl < 0,说明有其它线程正在初始化或者扩容,让出 CPU,让其它线程先执行完
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        // CAS 方式获取到锁,那么由该线程进行初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
   
            try {
   
   				// 将 table 赋值给 tab,判断 tab 是否已经初始化
                if ((tab = table) == null || tab.length == 0) {
   
                	// sc = sizeCtl > 0 表示已经初始化,否则使用默认容量 16。
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // sc = 0.75*n,sc 表示阈值
                    sc = n - (n >>> 2);
                }
       
            } finally {
   
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

key 对应到哈希桶的过程

  • hash 过程与 HashMap 类似,只是多了与 HASH_BITS 进行位运算

  • HASH_BITS 除了首位是 0,剩下的都是 1,按位与,得正数(首位为0);它的作用就是为了让上面的 hash 值为正数

// index 表示桶的位置
int index = spread(key.hashCode) & (length - 1)

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

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

get 方法

  1. key,value 都不能为 null,key 为 null 时抛出 NullPointerException

  2. 将 key 进行 hash 然后找到数组位置处的索引 index

  3. 比较 Hash 值,并且若 index == key || equals 返回 true,直接返回 index 处元素

  4. 若 index 处的 hash 值小于 0,表示正在扩容,进一步调用 Node 子类的 find 方法

  5. index 处的 hash 值不小于 0,通过链表 Next 指针遍历查找

static final int TREEBIN   = -2; // hash for roots of trees

public V get(Object key) {
   
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 计算 hash 值
    int h = spread(key.hashCode());
    // 数组不为空 && 数组长度大于 0 && 该位置已经有元素
    if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
   
    	// 如果 key 的 hash 值一样,则进一步判断
        if ((eh = e.hash) == h) {
   
        	// 如果是同一个 key 或者 equal 方法返回 true
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
            	// 返回对应 value
                return e.val;  
        }
        // 如果 hash 值小于 0,表示数组正在扩容
        // 此处 Node 节点为 ForwardingNode,调用 ForwardingNode 的 find 方法
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // eh > 0 情况,链表遍历    
        while ((e = e.next) != null) {
   
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    // 没有元素
    return null;
}

ForwardingNode 介绍

ForwardingNode 是 Node 子类之一,当节点的 Hash 值为 -1 时,该节点就为 ForwardingNode ,标记此处正在扩容

  • 外层循环用于刷新 ForwardingNode 所在的 table 情况,因为扩容过程中 table 的节点情况在变化
  • 内层循环则去以 next 指针的形式遍历 table,直到找到 hashCode 相同 && (== || equals )返回 true 的节点
static final class ForwardingNode<K,V> extends Node<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值