声明:本文只有源码分析注释,提供正在研究ConcurrentHashMap源码的同学参考!!!

1、内部结构:

2、Segment 分析

 static final class Segment<K,V> extends ReentrantLock implements Serializable {// 尝试获取锁最大次数,可以理解为自旋次数static final int MAX_SCAN_RETRIES =Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;// 用来存放key-value对象,并且扩容也是针对这个对象transient volatile HashEntry<K,V>[] table;// 元素个数transient int count;// 修改次数,和hashmap一样transient int modCount;// 扩容阀值 ,容量 * 加载因子transient int threshold;// 加载因子final float loadFactor;}

3、HashEntry 分析

  static final class HashEntry<K,V> {// hash 值final int hash;// 键final K key;// 值volatile V value;// 下一个元素volatile HashEntry<K,V> next;}

4、ConcurrentHashMap 基本分析

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>implements ConcurrentMap<K, V>, Serializable {// 默认HashEntry容量static final int DEFAULT_INITIAL_CAPACITY = 16;// 加载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认Segment容量,并发度,默认支持16个线程同时操作static final int DEFAULT_CONCURRENCY_LEVEL = 16;// ConcurrentHashMap最大容量static final int MAXIMUM_CAPACITY = 1 << 30;// Segment中HashEntry最小容量static final int MIN_SEGMENT_TABLE_CAPACITY = 2;// Segment最大容量static final int MAX_SEGMENTS = 1 << 16; // 非锁定情况下,调用size方法和contains方法的重试次数,如果超过该次数,直接全部上锁在进行操作static final int RETRIES_BEFORE_LOCK = 2;// 构造方法: 计算Segment、以及Segment对应的HashEntry数组容量大小,然后进行,并且初始化Segment中下标为0的元素public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {// 校验参数if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();//  并发级别,也就是对应Segment数组对应大小,如果超过最大容量,则直接取最大容量if (concurrencyLevel > MAX_SEGMENTS)concurrencyLevel = MAX_SEGMENTS;// int sshift = 0;// ssize = 大于等于并发级数的2的幂次方数int ssize = 1;// while 去找,while (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}this.segmentShift = 32 - sshift;// segment长度 - 1 ,用来计算下标:hash & segmentMaskthis.segmentMask = ssize - 1;// 校验是否大于最大容量if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// 这一整块代码是用来计算最后HashEntry数组容量的值,cap// 先用HashEntry数组大小 / Segment数组大小,默认 16 / 16 = 1int c = initialCapacity / ssize;// 判断计算结果c & Segment数组大小,是否HashEntry数组大小,如果小于c + 1 ,// 向上取整,假设initialCapacity = 17,而ssize 只有16个,那么Segment下面就不够存放17个HashEntry,所以这里就需要这么操作// 1 * 16 < 17,最后c的结果就是2if (c * ssize < initialCapacity)++c;// cap 默认等于HashEntry最小值 2 int cap = MIN_SEGMENT_TABLE_CAPACITY;// 如果cap 小于 c,则cap需要左移动来获取最终HashEntry的值while (cap < c)// cap最后的结果是2的幂次方数cap <<= 1;// 初始化Segment第0个对象,以后调用put方式时候,需要添加到其他Segment中,如果Segment为null,// 则参考Segment下标为0这个对象的属性,避免重复计算Segment、HashEntry容量值Segment<K,V> s0 =new Segment<K,V>(loadFactor, (int)(cap * loadFactor),(HashEntry<K,V>[])new HashEntry[cap]);// 创建Segment对象    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];// 利用UNSAFE类,直接把s0赋值到ss当中UNSAFE.putOrderedObject(ss, SBASE, s0);// 最后把当前ConcurrentHashMap中的segments属性赋值this.segments = ss;}

5、Put 分析

 public V put(K key, V value) {Segment<K,V> s;// 如果value为null,直接抛出异常if (value == null)throw new NullPointerException();// 获取key 对应的hey值int hash = hash(key);// 计算该key存放segment对应的下标, hahs & segmentMask(segment数组长度 -  1)int j = (hash >>> segmentShift) & segmentMask;// 查看第j个位置的segment的元素是否为空,如果为空则去创建if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null) s = ensureSegment(j);// 调用segment元素的put方法,插入到链表中return s.put(key, hash, value, false);}

6、ensureSegment 分析

// 创建Segment 对象,通过UnSafe类保证线程安全private Segment<K,V> ensureSegment(int k) {// 获取整个segment数组对象final Segment<K,V>[] ss = this.segments;// 获取偏移量,帮助UnSafe类来定位数组中具体的位置long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K,V> seg;// 判断当前位置的segment是否为空if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {// 以segment[0]为基础,来创建其他segment对象Segment<K,V> proto = ss[0]; // 取Segment[0]中HashEntry数组大小int cap = proto.table.length;// 取Segment[0]中的加载因子float lf = proto.loadFactor;// 计算扩容阀值int threshold = (int)(cap * lf);// 创建HashEntry对象HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];// 再一起重复检查,尽量提高程序效率if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck// 检查两次后仍为空,创建新的Segment对象Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);// 通过CAS方式,将新增的segment元素放入到segment数组当中while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) {if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))break;}}}// 返回对象return seg;}

7、put 添加元素

// 添加元素final V put(K key, int hash, V value, boolean onlyIfAbsent) {// 先尝试获取锁,tryLock()直接返回结果,不阻塞// 如果拿到锁则可以进行执行,否则调用scanAndLockForPut(key, hash, value)方法进行其他操作HashEntry<K,V> node = tryLock() ? null :scanAndLockForPut(key, hash, value);V oldValue;try {// 获取当前Segment对象的table元素HashEntry<K,V>[] tab = table;// 计算当前key所存放在HashEntry对应下标位置int index = (tab.length - 1) & hash;// 获取存放具体元素的链表,头节点HashEntry<K,V> first = entryAt(tab, index);// 遍历链表元素for (HashEntry<K,V> e = first;;) {// 判断链表元素是否为空if (e != null) {K k;// 判断是否相同key的元素if ((k = e.key) == key ||(e.hash == hash && key.equals(k))) {// 记录之前的值oldValue = e.value;// 如果onlyIfAbsent = false,则会替换之前key的值,并且修改次数+1// 如果为ture,则不进行替换,直接结束循环// 主要功能是为了:在put时,判断需不需要如果key重复就不需要进行修改if (!onlyIfAbsent) {e.value = value;++modCount;}break;}// 元素后移,继续循环e = e.next;}else {// 这里node不等于null的情况,是因为最开始没有获取到锁,然后进行了创建if (node != null)node.setNext(first);else// 线程第一次正常拿到锁执行到这,node应该是null,然后进行创建 头插法node = new HashEntry<K,V>(hash, key, value, first);// 节点数量 + 1 int c = count + 1;// 判断是否需要扩容,这里的扩容只是针对HashEntry数组// 当前HashEntry全部元素 > 扩容阀值 && 当前HashEntry数组大小 < 最大容量if (c > threshold && tab.length < MAXIMUM_CAPACITY)// 扩容rehash(node);else// 将当前新元素设置成tab数组中index下标的第一个元素,成为头节点setEntryAt(tab, index, node);// 修改次数++++modCount;// 更新节点数量count = c;// 老的元素赋值为空oldValue = null;break;}}} finally {// 解锁unlock();}// 返回老的值return oldValue;}

8、scanAndLockForPut 未获取到锁执行的方法

// 当put时,线程没有获取到锁的时候,执行这个方法private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {// 获取当前segment中HashEntry第一个元素HashEntry<K,V> first = entryForHash(this, hash);HashEntry<K,V> e = first;HashEntry<K,V> node = null;// 用来控制循环流程标识int retries = -1; // 循环尝试获取锁,如果获取到了结束循环,如果没有获取到执行while中的逻辑while (!tryLock()) {HashEntry<K,V> f; // 第一次retires默认=-1,if (retries < 0) {// e == null 分为两种情况:// 1、当前HashEntry元素是初始化状态,元素为空// 2、遍历完整个链表,一直到链表尾部if (e == null) {if (node == null)// 获取一个新节点node = new HashEntry<K,V>(hash, key, value, null);// 并且把流程控制状态为0retries = 0;}// 如果元素不为空,则判断key是否相等else if (key.equals(e.key))retries = 0;// 以上条件不满足,则元素后移,继续循环elsee = e.next;}// 控制循环次数,当retries操作数,大于了最大自旋数,则进行lock进行阻塞else if (++retries > MAX_SCAN_RETRIES) {// 如果lock没获取到锁,则进行阻塞,阻塞被唤醒获取到锁之后,终止循环lock();break;}// 这里判断是因为:如果在while循环当中,可能链表结构被其他线程所修改了,所以这里会进行最新的头节点,和之前获取的头节点进行判断// (retries & 1) == 0 ,判断奇偶数,当retries为偶数的时候,条件成立// (f = entryForHash(this, hash)) != first ,获取最新的当前链表头节点,与之前对比// 如果被修改,else if ((retries & 1) == 0 &&(f = entryForHash(this, hash)) != first) {// 把e 、first 全部更新为最新的头节点e = first = f;// 改为-1,重新遍历链表retries = -1;}}return node;}

8、rehash 扩容

private void rehash(HashEntry<K,V> node) {// 记录老的table数组HashEntry<K,V>[] oldTable = table;// 记录老的容量int oldCapacity = oldTable.length;// 获取新数组的容量,之前的两倍int newCapacity = oldCapacity << 1;// 计算新的阀值threshold = (int)(newCapacity * loadFactor);// 创建新容量的HashEntry数组HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) new HashEntry[newCapacity];// 用来计算下标int sizeMask = newCapacity - 1;// 遍历老的table进行元素转移for (int i = 0; i < oldCapacity ; i++) {// 获取每个数组中的链表元素HashEntry<K,V> e = oldTable[i];// 为空就没啥东西转移的了if (e != null) {// 获取头节点的下一个节点HashEntry<K,V> next = e.next;// 计算下标int idx = e.hash & sizeMask;// 如果next == null,则表示链表元素只有一个if (next == null) // 直接把当前元素转移到新数组上面即可newTable[idx] = e;else {// 如果不止一个就需要进行转移了// 先把头节点赋值给lastRun,以及新数组中的下标HashEntry<K,V> lastRun = e;int lastIdx = idx;// 遍历每一个元素,找下标相同的元素,以最后一组为准for (HashEntry<K,V> last = next; last != null; last = last.next) {int k = last.hash & sizeMask;if (k != lastIdx) {lastIdx = k;lastRun = last;}}// 先把找到相同下标的元素转移过去newTable[lastIdx] = lastRun;// 再把lastRun之前的元素,分别放入新的元素for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {V v = p.value;int h = p.hash;int k = h & sizeMask;// 头插法HashEntry<K,V> n = newTable[k];newTable[k] = new HashEntry<K,V>(h, p.key, v, n);}}}}// 扩容完成还需要将新的元素添加到链表当中int nodeIndex = node.hash & sizeMask; // add the new nodenode.setNext(newTable[nodeIndex]);newTable[nodeIndex] = node;// 把当前segment的table 更新成扩容后的元素table = newTable;}

9、get 方法

 public V get(Object key) {Segment<K,V> s; // manually integrate access methods to reduce overheadHashEntry<K,V>[] tab;int h = hash(key);long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;// 判断当前segment对象不为空,并且segment中的table不为空if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&(tab = s.table) != null) {// 遍历链表找元素for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next) {K k;if ((k = e.key) == key || (e.hash == h && key.equals(k)))return e.value;}}return null;}

10、size 方法

public int size() {// 获取当前整个segment数组final Segment<K,V>[] segments = this.segments;// 声明变量,便于统计int size;boolean overflow; // true if size overflows 32 bitslong sum;         // sum of modCountslong last = 0L;   // previous sumint retries = -1; // first iteration isn't retrytry {// 上来就一个死循环for (;;) {// 判断循环次数是否等于自旋次数if (retries++ == RETRIES_BEFORE_LOCK) {// 对每个segment对象进行加锁,加锁后再进行统计for (int j = 0; j < segments.length; ++j)ensureSegment(j).lock(); // force creation}sum = 0L;size = 0;overflow = false;// 对每个segmentAt的数量进行统计for (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {// 进行统计sum += seg.modCount;int c = seg.count;if (c < 0 || (size += c) < 0)overflow = true;}}// 如果循环两次的结果都是一样,则表示没有线程进行操作,直接返回// 如果不一致,则继续重新赋值循环操作,超过一定的循环次数就整个加锁进行统计if (sum == last)break;last = sum;}} finally {// 循环进行解锁操作if (retries > RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)segmentAt(segments, j).unlock();}}return overflow ? Integer.MAX_VALUE : size;}

ConcurrentHashMap1.7 最最最最最详细源码分析相关推荐

  1. jQuery deferred应用dom加载完毕详细源码分析(三)

    我承认上章ajax部分写得不好,不要怪我,它的ajax代码太多了,而且跨越大,方法跳跃多,实在不好排版与讲解,但如果你真正想研究源码并且仔细读了得话,你的 收获应该会很大,至少你明白了js的ajax是 ...

  2. AM335x启动流程(BootRom-MLO-Uboot)超详细源码分析

    转载地址:https://blog.csdn.net/p942005405/article/details/83376464 写的非常好,收藏学习 参考文件: 1,AM335x ARM Cortex- ...

  3. android framework车载桌面CarLauncher的TaskView详细源码分析

    1.构建相关的TaskView,装载到对应的ViewGroup b站免费视频教程讲解: https://www.bilibili.com/video/BV1wj411o7A9/ //packages/ ...

  4. ConcurrentHashMap1.7和1.8的源码分析比较

    ConcurrentHashMap 在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap,为 ...

  5. laravel $request 多维数组取值_Laravel 运行原理分析与源码分析,底层看这篇足矣

    精选文章内容 一.运行原理概述 laravel的入口文件 index.php 1.引入自动加载 autoload.php 2.创建应用实例,并同时完成了: 基本绑定($this.容器类Containe ...

  6. jdk1.8 源码分析导图

    以下总结全部基于 jdk1.8,详细源码分析见 GitHub 链接:https://github.com/zchen96/jdk1.8-source-code-read 一.非并发 幕布导图链接:ht ...

  7. Java的三种代理模式完整源码分析

    Java的三种代理模式&完整源码分析 Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCach ...

  8. Java的三种代理模式【附源码分析】

    Java的三种代理模式&完整源码分析 代理模式分为两种,静态代理和动态代理,动态代理包括JDK动态代理和Cglib动态代理. 静态代理 静态代理在使用时,需要定义接口或者父类,被代理对象与代理 ...

  9. UDT 最新源码分析(三) -- UDT Socket 相关函数

    UDT 最新源码分析 -- UDT Socket 相关函数 UDT socket 建立与使用 主要流程 C/S 模式 Rendezvous 模式 UDT epoll UDT socket 创建 UDT ...

最新文章

  1. python二十二:迭代,三元表达式,列表解析
  2. 科普|推荐系统常用算法总结
  3. 3.5 《数据库系统概论》之基本表更新(INSERT、UPDATE、ALTER、DELETE)与视图VIEW(定义、查询、更新)
  4. 关于chrome控制台警告:Synchronous XMLHttpRequest on the main thread终极解决办法
  5. java swing removeall_java中JFrame中函数removeAll的用法 | 学步园
  6. SQLLite数据库操作
  7. expdp 详解及实例
  8. 跨域请求的常用方式及解释
  9. C++多态虚函数demo
  10. 博弈论(一)基本概念
  11. axure 鼠标样式_Axure教程:简单开关按钮的实现
  12. torch中permute()函数用法
  13. java hdporn,docs/java/concurrent/SynBottom.md · wt1814/wt-note - Gitee.com
  14. 重磅!上海985教授当选!全球仅4人!
  15. 如何把团队带成一盘散沙?
  16. 泉州地区的“会子”是一种怎样的制度?
  17. 有限视图(Limited View)断层重建--CasRedSCAN论文阅读
  18. 每天读一点职场心理学读书笔记
  19. 扩展欧几里得算法 | exgcd 证明 + 板子 + 习题
  20. TransReID学习记录

热门文章

  1. 关于移动宽带连不上某些网站的解决办法
  2. python正则去掉重复单词_python使用正则表达式去除中文文本多余空格,保留英文之间空格方法详解 | 学步园...
  3. java毕业设计车辆违规信息管理系统Mybatis+系统+数据库+调试部署
  4. 韩语学习之——韩语基础入门第二课基本辅音
  5. U盘文件变成快捷方式的解决方案
  6. GIC通用中断控制器
  7. 泰山杯练习平台部分题目wp
  8. 51虚拟安卓系统v1.1.0.6-安卓端的虚拟机(支持root,xposed框架)
  9. 严正警告!!独处一室的人,请一定不要看文中介绍的URL,未成年也不能看![更新]
  10. Internal error. Please report to https://code.google.com/p/android/issues