ConcurrentHashMap源码阅读

本文深入探讨了JDK8中的ConcurrentHashMap,包括其重要属性如sizeCtl、table和nextTable的用途和线程安全性。介绍了构造器、put流程以及扩容机制,特别是当链表长度超过8时转为红黑树的优化。同时提到了ForwardingNode在扩容期间的作用,保证在扩容过程中不会阻塞查询操作。
摘要由CSDN通过智能技术生成

1. JDK8中的ConcurrentHashMap

1. 1  重要属性

  • private transient volatile int sizeCtl; 
    • 默认为 0
    • 当初始化时, 为 -1
      • U.compareAndSwapInt(this, SIZECTL, sc, -1) 
      • Unsafe,CAS操作
        • sizeCtl==sc,返回True并将sizeCtl的值置-1
        • sizeCtl!=sc,返回false,循环继续尝试
        • volatile 修饰配合CAS操作保证线程安全
      • 当扩容时, 为 -(1 + 扩容线程数)
      • 当初始化或扩容完成后,为下一次的扩容的阈值大小
        • sc = n - (n >>> 2)
        • 懒惰初始化,下一次扩容阈值大小(容量的3/4)
  • private static final int DEFAULT_CAPACITY = 16;//默认初始容量16
  • static final int MOVED = -1;//hash for forwarding nodes
  • static final int TREEBIN = -2;//hash for roots of trees
  • transient volatile Node<K,V>[] table;//hash 表
  • private transient volatile Node<K,V>[] nextTable;//扩容时的 新 hash 表
  • static final class ReservationNode<K,V> extends Node<K,V> {}
    • 用在 compute 以及 computeIfAbsent 时, 用来占位, 计算完成后替换为普通 Node
  • static final class TreeBin<K,V> extends Node<K,V> {}
    • 作为 treebin 的头节点, 存储 root 和 first
    • 链表过长,O(1)-->O(n)链表过长使用红黑树,O(n)-->O(logn)
    • 红黑树使用条件
      • 先扩容,哈希表长度到64,当前某个节点链表长度大于8,将链表转换为红黑树;
      • 链表长度小于6时将红黑树转换为链表
  • static final class TreeNode<K,V> extends Node<K,V> {}
    • 作为 treebin 的节点, 存储 parent, left, right

 1.2 构造器分析

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel)   // 如果初始容量的值小于并发度
        initialCapacity = concurrencyLevel;   // 将初始容量的值设置为并发度
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
    this.sizeCtl = cap;
}
//指定容量:容量为大于当前值的2的幂,例如构造器传入3,初始容量返回4
private static final int tableSizeFor(int c) {//c=3(11) 说明:括号内为二进制
    int n = c - 1;//n=2(10)
    n |= n >>> 1;// n>>>1=1(01),n|n>>>1 = 3(11)
    n |= n >>> 2;// n>>>2=0(00),n|n>>>2 = 3(11)
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    //返回4,保证了2的幂数
    //扩容时为原容量的2倍
}

1.3 put流程分析

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    //如果key或者value其中一个null,就报空指针异常
    int hash = spread(key.hashCode());
    // spread:(h ^ (h >>> 16)) & HASH_BITS
    // HASH_BITS:正常节点哈希的可用位,会综合高位低位, 具有更好的 hash 性
    // 保障正整数(负数也其他用处)
    // MOVED = -1; // hash for forwarding nodes
    // TREEBIN = -2; // hash for roots of trees
    // RESERVED = -3; // hash for transient reservations
    int binCount = 0;
    for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
        ConcurrentHashMap.Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
            //  初始化创建hash表,
            //  若有一个线程正在创建table,其它线程会在 while() 循环中 yield 直至hash表的创建
            //  U.compareAndSwapInt(this, SIZECTL, sc, -1)
            //  使用CAS保证只有一个线程进入条件,将sizeCtl置-1,进行table的创建
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 要创建链表头节点
            // 获取 Node[] 中第 i 个 Node(链表头),如果为空,说明可以插入
            // U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值