ConcurrentHashMap源码解析

作者: 沐风之境

ConcurrentHashMap源码解析

ConcurrentHashMap是什么?

它是对HashMap线程安全性的增强类,保证了Map对象在多线程环境下的读写的线程安全性。在使用方法上和HashMap保持一致,都是Map接口的实现类。

类结构

ConcurrentHashMap

核心数据结构

核心数据结构和HashMap相同,都是采用数组链表或数组红黑树的结构

核心源码解读(源码来自JDK11)

initTable方法

initTable()方法用于核心数据结构对象数组的初始化操作

// Table初始化和扩容时的控制,-1为正在有线程对其进行初始化;-N 说明有N-1个线程正在进行扩容;大于等于0的情况表示table的容量
private transient volatile int sizeCtl;

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

// 初始化操作
private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  while ((tab = table) == null || tab.length == 0) {
    // 判断是否有其他线程在进行初始化操作
    if ((sc = sizeCtl) < 0)
      // 放弃线程竞争,仅空转
      Thread.yield(); // lost initialization race; just spin

    // 使用Java内部的本地方法,比较SIZECTL与预期值sc是否相同,相同的话,将sizeCtl更新为-1,成功返回true
    else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
      try {
        if ((tab = table) == null || tab.length == 0) {
          // 算得初始化容量n
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          // 初始化Node数组
          @SuppressWarnings("unchecked")
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          // 将初始化好的Node数组赋值给table成员变量
          table = tab = nt;
          // sc = n * (3/4); sc变为原来的0.75倍,对应默认的负载因子。表示下次扩容的触发的真实元素个数
          sc = n - (n >>> 2);
        }
        // 设置sizeCtl为sc
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

put方法

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

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;;) {
    // 定位到的头元素f
    Node<K,V> f; int n, i, fh; K fk; V fv;
    // 如果当前的table是空数组
    if (tab == null || (n = tab.length) == 0)
      // 自旋CAS初始化数组
      tab = initTable();
    // 使用hash与数组长度取模,获取指定位置的桶
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
      // 桶的位置是空的情况,直接放入新元素
      if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        // 加完后直接跳出
        break;                   // no lock when adding to empty bin
    }
    else if ((fh = f.hash) == MOVED)
      // 桶元素的hash是否为-1,表示当前对象正在被其他线程扩容,当前线程协助其他线程加快扩容速度
      tab = helpTransfer(tab, f);
    // 桶元素的hash、key是否和待put的元素是否相等,如果相等就没必要加了,直接返回
    else if (onlyIfAbsent // check first node without acquiring lock
             && fh == hash
             && ((fk = f.key) == key || (fk != null && key.equals(fk)))
             && (fv = f.val) != null)
      return fv;
    else {
      V oldVal = null;
      // 对桶元素进行同步操作协调
      synchronized (f) {
        if (tabAt(tab, i) == f) {
          if (fh >= 0) {
            // 该桶为一个链表
            binCount = 1;
            // 遍历该链表的操作,和待put的元素的hash和key进行比较
            for (Node<K,V> e = f;; ++binCount) {
              K ek;
              // 如果存在则替换
              if (e.hash == hash &&
                  ((ek = e.key) == key ||
               (ek != null && key.equals(ek)))) {
            oldVal = e.val;
            if (!onlyIfAbsent)
              e.val = value;
            break;
          }
          Node<K,V> pred = e;
          // 不存在的话,用在链表的末尾加入当前元素
          if ((e = e.next) == null) {
            pred.next = new Node<K,V>(hash, key, value);
            break;
          }
        }
      }
      // 是红黑树的结构
      else if (f instanceof TreeBin) {
        Node<K,V> p;
        binCount = 2;
        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                              value)) != null) {
          oldVal = p.val;
          if (!onlyIfAbsent)
            p.val = value;
        }
      }
      else if (f instanceof ReservationNode)
        throw new IllegalStateException("Recursive update");
    }
  }
  if (binCount != 0) {
    // 如果链表节点长度大于等于树化阈值将链表转换为红黑二叉树
    if (binCount >= TREEIFY_THRESHOLD)
      treeifyBin(tab, i);
    if (oldVal != null)
      return oldVal;
    break;
  }
}

} addCount(1L, binCount); return null; }

final Node[] helpTransfer(Node[] tab, Node f) {

Node<K,V>[] nextTab; int sc;
// 如果 table 不是空 且 node 节点是转移类型,数据检验
// 且 node 节点的 nextTable(新 table) 不是空,同样也是数据校验
// 尝试帮助扩容
if (tab != null && (f instanceof ForwardingNode) &&
    (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
    // 根据 length 得到一个标识符号
    int rs = resizeStamp(tab.length);
    // 如果 nextTab 没有被并发修改 且 tab 也没有被并发修改
    // 且 sizeCtl  < 0 (说明还在扩容)
    while (nextTab == nextTable && table == tab &&
           (sc = sizeCtl) < 0) {
        // 如果 sizeCtl 无符号右移  16 不等于 rs ( sc前 16 位如果不等于标识符,则标识符变化了)
        // 或者 sizeCtl == rs + 1  (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
        // 或者 sizeCtl == rs + 65535  (如果达到最大帮助线程的数量,即 65535)
        // 或者转移下标正在调整 (扩容结束)
        // 结束循环,返回 table
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            // 如果以上都不是, 将 sizeCtl + 1, (表示增加了一个线程帮助其扩容)
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                // 进行转移
                transfer(tab, nextTab);
                // 结束循环
                break;
            }
        }
        return nextTab;
    }
    return table;
}

get方法

public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  // 计算key的hash值
  int h = spread(key.hashCode());
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (e = tabAt(tab, (n - 1) & h)) != null) {
    if ((eh = e.hash) == h) {
          // 桶元素的hash等于当前key的hash, key也相等的情况下找到目标元素
      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
        return e.val;
    }
    // 正在扩容或者是红黑树
    else if (eh < 0)
      return (p = e.find(h, key)) != null ? p.val : null;
    // 沿链表寻找hash和key匹配的元素,匹配到就返回
    while ((e = e.next) != null) {
      if (e.hash == h &&
          ((ek = e.key) == key || (ek != null && key.equals(ek))))
        return e.val;
    }
  }
  return null;
}

参考

更多推荐

更多
  • Dubbo源码解读与实战-41加餐:一键通关服务发布全流程 41 加餐:一键联 Dubbo 中的这些核心实现,分析 Dubbo**服务发布** 和**服务引用**的全流程,帮助你将之前课时介绍的独立知识点联系起来,形成一个完整整体。 本课时我们就先来重点关注 Provider 节点发布服务的过
  • Dubbo源码解读与实战-42加餐:服务引用流程全解析 42 加餐:已经分析了服务发布的核心流程。那么在本课时,我们就接着深入分析**服务引用的核心流程**。 Dubbo 支持两种方式引用远程的服务: **服务直连的方式** ,仅适合在**调试服务**的时候使用; **基于注册中心引用服
  • Dubbo源码解读与实战-36负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下) 36 负载均衡:公平公正物尽其用的负载均衡策stractLoadBalance 抽象类的内容,还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。
  • Dubbo源码解读与实战-47配置中心设计与实现:集中化配置and本地化配置,我都要(上) 47 配置中心设计与实现:集中化配置 and 本地化*,在服务自省架构中也依赖配置中心完成 Service ID 与 Service Name 的映射。配置中心在 Dubbo 中主要承担两个职责: 外部化配置; 服务治理,负责服务治
  • Dubbo源码解读与实战-02Dubbo的配置总线:抓住URL,就理解了半个Dubbo 02 Dubbo 的配置总线:抓住 URL,就理解置总线:抓住 URL,就理解了半个 Dubbo 。 在互联网领域,每个信息资源都有统一的且在网上唯一的地址,该地址就叫 URL(Uniform Resource Locator,统一资
  • Dubbo源码解读与实战-08代理模式与常见实现 08 代理模式与常见实现te 等常用的开源框架,都使用到了动态代理机制。当然,Dubbo 中也使用到了动态代理,在后面开发简易版 RPC 框架的时候,我们还会参考 Dubbo 使用动态代理机制来屏蔽底层的网络传输以及服务发现的相关实现。
  • Dubbo源码解读与实战-48配置中心设计与实现:集中化配置and本地化配置,我都要(下) 48 配置中心设计与实现:集中化配置 and 本地化接口以及 DynamicConfiguration 接口的实现,**其中 DynamicConfiguration 接口实现是动态配置中心的基础**。那 Dubbo 中的动态配置中心是
  • Dubbo源码解读与实战-30Filter接口,扩展Dubbo框架的常用手段指北 30 Filter 接口,扩展 DubborWrapper 的具体实现,这里简单回顾一下。在 buildInvokerChain() 方法中,ProtocolFilterWrapper 会加载 Dubbo 以及应用程序提供的 Filte
  • Dubbo源码解读与实战-31加餐:深潜Directory实现,探秘服务目录玄机 31 加餐:深潜 Directory 实现主题是:深潜 Directory 实现,探秘服务目录玄机。 在生产环境中,为了保证服务的可靠性、吞吐量以及容错能力,我们通常会在多个服务器上运行相同的服务端程序,然后以**集群**的形式对外提
  • Dubbo源码解读与实战-10Netty入门,用它做网络编程都说好(下) 10 Netty 入门,用它做网 的设计。在本课时,我们就深入到 Netty 内部,介绍一下 Netty 框架核心组件的功能,并概述它们的实现原理,进一步帮助你了解 Netty 的内核。 这里我们依旧采用之前的思路来介绍 Netty
  • 近期文章

    更多
    文章目录

      推荐作者

      更多