前言

每一次总结都意味着重新开始,同时也是为了更好的开始。ConcurrentHashMap 一直是我心中的痛。虽然不敢说完全读懂了,但也看了几个重要的方法,有不少我觉得比较重要的知识点。

然后呢,放一些楼主写的关于 ConcurrentHashMap 相关源码分析的文章链接:

  1. ConcurrentHashMap 扩容分析拾遗
  2. 并发编程——ConcurrentHashMap#addCount() 分析
  3. 并发编程——ConcurrentHashMap#transfer() 扩容逐行分析
  4. 并发编程——ConcurrentHashMap#helpTransfer() 分析
  5. 并发编程 —— ConcurrentHashMap size 方法原理分析
  6. 并发编程之 ConcurrentHashMap(JDK 1.8) putVal 源码分析
  7. 深入理解 HashMap put 方法(JDK 8逐行剖析)
  8. 深入理解 hashcode 和 hash 算法

putVal 方法总结

说起 ConcurrentHashMap ,当然从入口开始说。该方法要点如下:

  1. 不允许有 null key 和 null value。
  2. 只有在第一次 put 的时候才初始化 table。初始化有并发控制。通过 sizeCtl 变量判断(小于 0)。
  3. 当 hash 对应的下标是 null 时,使用 CAS 插入元素。
  4. 当 hash 对应的下标值是 forward 时,帮助扩容,但有可能帮不了,因为每个线程默认 16 个桶,如果只有 16个桶,第二个线程是无法帮助扩容的。
  5. 如果 hash 冲突了,同步头节点,进行链表操作,如果链表长度达到 8 ,分成红黑树。
  6. 调用 addCount 方法,对 size 加一,并判断是否需要扩容(如果是覆盖,就不调用该方法)。
  7. Cmap 的并发性能是 hashTable 的 table.length 倍。只有出现链表才会同步,否则使用 CAS 插入。性能极高。

size 方法总结

  1. size 方法不准确,原因是由于并发插入,baseCount 难以及时更新。计数盒子也难以及时更新。
  2. 内部通过两个变量,一个是 baseCount,一个是 counterCells,counterCells 是并发修改 baseCount 后的备用方案。
  3. 具体更新 baseCount 和 counterCells 是在 addCount 方法中。备用方法 fullAddCount 则会死循环插入。
  4. CounterCell 是一个用于分配计数的填充单元,改编自 LongAdder和Striped64。内部只有一个 volatile 的 value 变量,同时这个类标记了 @sun.misc.Contended,这是一个避免伪共享的注解,用于替代之前的缓存行填充。多线程情况下,注解让性能提升 5 倍。

helpTransfer 方法总结

  1. 当 Cmap 尝试插入的时候,发现该节点是 forward 类型,则会帮助其扩容。
  2. 每次加入一个线程都会将 sizeCtl 的低 16 位加一。同时会校验高 16 位的标示符。
  3. 扩容最大的帮助线程是 65535,这是低 16 位的最大值限制的。
  4. 每个线程默认分配 16 个桶,如果桶的数量是 16,那么第二个线程无法帮助其扩容。

transfer 方法总结

  1. 该方法会根据 CPU 核心数平均分配给每个 CPU 相同数量的桶。但如果不够 16 个,默认就是 16 个。
  2. 扩容是按照 2 倍进行扩容。
  3. 每个线程在处理完自己领取的区间后,还可以继续领取,如果有的话。这个是 transferIndex 变量递减 16 实现的。
  4. 每次处理空桶的时候,会插入一个 forward 节点,告诉 putVal 的线程:“我正在扩容,快来帮忙”。但如果只有 16 个桶,只能有一个线程扩容。
  5. 如果有了占位符,那就不处理,跳过这个桶。
  6. 如果有真正的实际值,那就同步头节点,防止 putVal 那里并发。
  7. 同步块里会将链表拆成两份,根据 hash & length 得到是否是 0,如果是0,放在低位,反之,反之放在 length + i 的高位。这里的设计是为了防止下次取值的时候,hash 不到正确的位置。
  8. 如果该桶的类型是红黑树,也会拆成 2 个,这是必须的。然后判断拆分过的桶的大小是否小于等于 6,如果是,改成链表。
  9. 线程处理完之后,如果没有可选区间,且任务没有完成,就会将整个表检查一遍,防止遗漏。

addCount 方法总结

  1. 当插入结束的时候,会对 size 进行加一。也会进行是否需要扩容的判断。
  2. 优先使用计数盒子(如果不是空,说明并发了),如果计数盒子是空,使用 baseCount 变量。对其加 X。
  3. 如果修改 baseCount 失败,使用计数盒子。如果此次修改失败,在另一个方法死循环插入。
  4. 检查是否需要扩容。
  5. 如果 size 大于等于 sizeCtl 阈值,且长度小于 1 << 30,可以扩容成 1 << 30,但不能扩容成 1 << 31。
  6. 如果已经在扩容,帮助其扩容,和 helpTransfer 逻辑一样。
  7. 如果没有在扩容,自行开启扩容,更新 sizeCtl 变量为负数,赋值为标识符高 16 位 + 2。

小结

ConcurrentHashMap 满是财富,都是精华代码,我们这次阅读只是管中窥豹,要知道其中包含 53 个类,6300 行代码,但这次确实收获很多。有时间一定再次阅读!!

能力不高,水平有限,有些地方确实理解不了 Doug Lea 大师的设计,如果有什么错误,还请大家指出。不胜感激。

ConcurrentHashMap 源码阅读小结的更多相关文章

  1. JDK12 concurrenthashmap源码阅读

           本文部分照片和代码分析来自文末参考资料        java8中的concurrenthashmap的方法逻辑和注解有些问题,建议看最新的JDK版本        建议阅读 concu ...

  2. ConcurrentHashMap源码阅读

    1. 前言 HashMap是非线程安全的,在多线程访问时没有同步机制,并发场景下put操作可能导致同一数组下的链表形成闭环,get时候出现死循环,导致CPU利用率接近100%. HashTable是线 ...

  3. JDK1.8 ConcurrentHashMap源码阅读

    1.  带着问题去阅读 为什么说ConcurrentHashMap是线程安全的?或者说 ConcurrentHashMap是如何防止并发的? 2.  字段和常量 首先,来看一下ConcurrentHa ...

  4. [PHP源码阅读]explode和implode函数

    explode和implode函数主要用作字符串和数组间转换的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出.在PHP中经常会用到这两个函数,因此 ...

  5. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  6. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  7. 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  8. SparkConf加载与SparkContext创建(源码阅读一)

    即日起开始spark源码阅读之旅,这个过程是相当痛苦的,也许有大量的看不懂,但是每天一个方法,一点点看,相信总归会有极大地提高的.那么下面开始: 创建sparkConf对象,那么究竟它干了什么了类,从 ...

  9. 初始化IoC容器(Spring源码阅读)

    初始化IoC容器(Spring源码阅读) 我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? ...

随机推荐

  1. VARCHAR的最大长度的问题

    大家知道,在SQL Server 2000中,VARCHAR的最大长度是8000,如果字符串的长度超过8000,保存在VARCHAR中时就会被截断.如果你需要传入的参数恰好很长,比如是一个xml,很多 ...

  2. ?js调用PHP里的变量,怎么弄?

    js调用PHP里的变量,怎么弄 网上给的例子都是js文件里一开始先给这个变量一个值,要是那样有啥意思啊,我要的就是可以变化的. hychyc_2008 | 浏览 2741 次  2013-04-18 ...

  3. Spring中ApplicationEvent和ApplicationListener封装

    1.测试程序EventTest.java,发布一个事件只需要调用FrameEventHolder.publishEvent()方法即可. package com.junge.spring.event; ...

  4. 实验6 LCD接口

    1.利用单片机控制LCD1602,在LCD1602上显示字符串,并使其整屏左移. #include<reg51.h> #define uchar unsigned char #define ...

  5. 【转】Windows Server 2016 安装 IIS 服务时提示指定备用源路径

    原文地址:http://www.codingwhy.com/view/973.html 在Windows Serever 2016中安装IIS的时候,遇到以下提示 是否需要指定备用源路径?一个或多个安 ...

  6. nodejs学习(imooc课程笔记, 主讲人Scott)

    课程地址: 进击Node.js基础(一) 进击Node.js基础(二) 1. nodejs创建服务器 var http = require('http'); //加载http模块 //请求进来时, 告 ...

  7. CopyOnWriteArraySet源码解析

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对CopyOnWriteArrayList底层不清楚的话,建议先去看看CopyOn ...

  8. Swift5 语言指南(二十五) 自动引用计数(ARC)

    Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...

  9. joi库 学习笔记

    零.背景 node.js 应用中,req.query / req.body 传来的参数需要做 valication( 合法性验证 ) 一.安装 https://github.com/hapijs/jo ...

  10. day 73 初学vue (1)

    前情提要: vue 框架的初学习, 主要是,指令,属性,函数,计算属性,监听属性,钩子,生命周期,过滤器,阻止事件和综合案例todo list 学习准备,感谢学习资源: vue 官网:https:// ...