ConcurrentHashMap源码解析(一): 常量、成员变量、内部类、部分方法

简介

ConcurrentHashMap是线程安全的Map,Jdk1.7之前采用的是Segment数组结构和HashEntry数组结构组成,Jdk1.8之后采用的是synchronized+数组+链表/红黑树组成。个人认为ConcurrentHashMap是JDK常用集合源码解析最难的,楼主也啃了好久,中途也放弃过,屡败屡战,终于完成了常用方法的解读,给大家分享出来,如有错误,还望不吝赐教,共同进步。
准备分几篇来介绍,首先介绍常量、成员变量、内部类和部分方法。

常量

 // 散列表数组(即table)长度最大限制
 private static final int MAXIMUM_CAPACITY = 1 << 30;
 // table默认长度
 private static final int DEFAULT_CAPACITY = 16;
 // 最大可能的数组长度,toArray方法使用(官方直译),常用方法中未涉及
 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 //  并发级别, 之前的版本留下来的, 无用
 private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
 // 负载因子
 private static final float LOAD_FACTOR = 0.75f;
 // 链表转红黑树的阈值,9个节点开始转
 static final int TREEIFY_THRESHOLD = 8;
 // 红黑树转链表的阈值
 static final int UNTREEIFY_THRESHOLD = 6;
 // 链表转红黑树是数组最小长度, 当最小长度达到64, 并且链表长度达到8, 才开始转红黑树
 static final int MIN_TREEIFY_CAPACITY = 64;
 // 并发扩容时最小步长
 private static final int MIN_TRANSFER_STRIDE = 16;
 // 扩容相关, 计算扩容时生成的一个标识戳, 全局不变
 private static int RESIZE_STAMP_BITS = 16;
 // 并发扩容的最大线程数, 最大为65535
 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
 // 扩容相关, 为16
 private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

 /** 节点哈希字段的编码 */
 // node节点的hash为-1时, 表示当前节点是FWD节点, 已经迁移完成
 static final int MOVED     = -1; 
 // node节点的hash为-2, 表示当前节点是TreeBin节点
 static final int TREEBIN   = -2; 
 // node节点的hash为-3, 表示当前节点为ReservationNode
 static final int RESERVED  = -3; /
 // 用来获取新的hash值, 可以将一个负数位与运算后得到正数
 static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
 // 当前系统的CPU数量
 static final int NCPU = Runtime.getRuntime().availableProcessors();

成员变量

  // table数组
 transient volatile Node<K,V>[] table;
 // 扩容过程中的新table, 扩容结束后会将nextTable设置为null
 private transient volatile Node<K,V>[] nextTable;
 // LongAdder中的baseCount, 未发生竞争时 或者 当前LongAdder处于加锁状态时, 增量累积到baseCount中
 private transient volatile long baseCount;
 /**
  * sizeCtl < 0
  * 1. -1表示当前table正在初始化(有线程正在创建table数组), 当前线程需要自旋等待
  * 2. 表示当前table数组正在进行扩容, 高16位表示扩容的标识戳, 低16位表示: (1 + nThread) 当前参与并发扩容的线程数量(这个状态最重要)
  *
  * sizeCtl = 0
  * 表示创建table数组时, 使用DEFAULT_CAPACITY为大小
  *
  * sizeCtl > 0
  * 1. 如果table为初始化, 表示初始化大小
  * 2. 如果table已经初始化, 表示下次扩容时的阈值(n * 0.75)
  */
 private transient volatile int sizeCtl;
 // 扩容过程中, 记录当前进度, 所有线程都需要从transferIndex中分配区间任务, 去执行自己相应的区间
 private transient volatile int transferIndex;

 /**
  * LongAdder中的cellBusy
  * 0 表示当前LongAdder对象无锁状态
  * 1 表示当前LongAdder对象加锁状态, 只有一个对象能持有加锁状态
  */
 private transient volatile int cellsBusy;

 /**
  * LongAdder中的cells数组, 当baseCount发生竞争后, 会创建cells数组
  * 线程会通过计算hash值, 取到自己的cell, 将增量累加到指定cell中
  * 总数 = sum(cells) + baseCount
  */
 private transient volatile CounterCell[] counterCells;
 
 // -----------------   变量内存地址   -------------------
 // 操作内存地址
 private static final sun.misc.Unsafe U;
 // sizeCtl属性在ConcurrentHashMap中内存偏移地址
 private static final long SIZECTL;
 // transferIndex属性在ConcurrentHashMap中内存偏移地址
 private static final long TRANSFERINDEX;
 // baseCount属性在ConcurrentHashMap中内存偏移地址
 private static final long BASECOUNT;
 // cellsBusy属性在ConcurrentHashMap中内存偏移地址
 private static final long CELLSBUSY;
 // value属性在ConcurrentHashMap中内存偏移地址
 private static final long CELLVALUE;
 // table数组第一个元素的偏移地址
 private static final long ABASE;
 // table数组第一个元素二进制编码中第一个为1的位置
 private static final int ASHIFT;

内部类

链表节点Node

 static class Node<K,V> implements Map.Entry<K,V> {
      // 节点hash值,经过扰动运算得出来的
      final int hash;
      final K key;
      volatile V val;
      // 后继节点
      volatile Node<K,V> next;

      Node(int hash, K key, V val, Node<K,V> next) {
          this.hash = hash;
          this.key = key;
          this.val = val;
          this.next = next;
      }
 }

红黑树节点TreeNode

 static final class TreeNode<K,V> extends Node<K,V> {
 	   // 父节点
       TreeNode<K,V> parent; 
       // 左子节点
       TreeNode<K,V> left;
       // 右子节点
       TreeNode<K,V> right;
       TreeNode<K,V> prev;    // needed to unlink next upon deletion
       boolean red;

       TreeNode(int hash, K key, V val, Node<K,V> next,
                TreeNode<K,V> parent) {
           super(hash, key, val, next);
           this.parent = parent;
       }
 }

红黑树链表节点TreeBin

 static final class TreeBin<K,V> extends Node<K,V> {
      // 红黑树根节点
      TreeNode<K,V> root;
      // 链表的头节点
      volatile TreeNode<K,V> first;
      // 等待者线程(当前lockState是读锁状态)
      volatile Thread waiter;
      /**
       * 1. 写锁状态 写是独占状态, 以table来看, 真正进入到TreeBin中的写线程, 同一时间只有一个
       * 2. 读锁状态 读锁是共享的, 同一时刻可以有多个线程, 同时进入到TreeBin对象中获取数据, 每一个线程都会给lockState + 1
       * 3. 等待者状态(写线程在等待), 当TreeBin中有读线程目前正在读取数据时, 写线程无法修改数据, 那么就将lockState的最低两位设置为0b,10
       */
      volatile int lockState;
      // 锁状态的;值
      static final int WRITER = 1; 
      static final int WAITER = 2; 
      static final int READER = 4; 
 }

扩容完成的红黑树节点ForwardingNode

 static final class ForwardingNode<K,V> extends Node<K,V> {
 	  // 扩容中临时table数组
      final Node<K,V>[] nextTable;
      // ForwardingNode是node的子类, hash值为moved(-1), nextTable指向了扩容是临时存储node的新table
      // 写线程: 需要参与并发扩容 读线程: 调用find方法到新表继续查询
      ForwardingNode(Node<K,V>[] tab) {
          super(MOVED, null, null, null);
          this.nextTable = tab;
      }
 }

部分方法

扰动运算

 static final int spread(int h) {
 	 // 将原hash与原hash的高16位进行异或, 然后再和HASH_BITS进行位与预算
     return (h ^ (h >>> 16)) & HASH_BITS;
 }

在 table 的长度较小的时候,让高位也参与运算,降低 hash 冲突的概率。

获取>=c 最小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;
 }

获取数组指定下标位置i的元素

 @SuppressWarnings("unchecked")
 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);
 }

使用Unsafe通过内存地址的操作来完成,Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

通过CAS将table中指定位置i设置为新值v

 // c:期望值,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);
 }

table数组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);
 }

扩容标识戳

 // 扩容标识戳,标识戳一致时才能参与扩容
 static final int resizeStamp(int n) {
     // numberOfLeadingZeros: 这个数据的二进制串中从最左边算起连续的“0”的总数量。因为int类型的数据长度为32所以高位不足的地方会以“0”填充
     return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
 }

部分内容参考土拨鼠饲养员博主的博客,给了楼主学习ConcurrentHashMap很大的帮助, 博客地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值