落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单

之前在写HashMap的底层实现原理和设计背景的时候(看我主页置顶文章) , 有读者朋友反馈想看ConcurrentHashMap方面的文章 , 今天为大家带来这篇文章 。
ConcurrentHashMap相对HashMap来说要复杂的多 , HashMap涉及到的知识点相对较少 , 无非就是数组、链表、红黑树、哈希碰撞、扩容这些东西 , 但是ConcurrentHashMap涉及到的知识点却比这些要多 , 因为一旦涉及到多线程环境下的并发安全 , 并发、同步、锁这些概念就得了解 。
而在实际coding中 , HashMap使用频率很高 , ConcurrentHashMap却很低甚至没有 , 这就增加了我们学习的门槛 。
落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单1、学习思路学习ConcurrentHashMap之前 , 必须要清晰掌握HashMap的实现原理!
因为HashMap不是线程安全的 , 所以我们需要有一个线程安全的HashMap , 能够保证线程安全的有:
HashTable、
Collections.synchronizedMap()、
ConcurrentHashMap ,
然而HashTable、Collections.synchronizedMap()的实现方式是直接上锁的 , 性能方面没有做到最优解 , 因此我们需要一个性能更好的 , 这就是ConcurrentHashMap产生的背景 。
所以我们需要搞清楚以下一些问题:

  1. HashMap的底层原理;
  2. HashMap的哪些地方是线程不安全的?
  3. HashTable、Collections.synchronizedMap()、ConcurrentHashMap都是如何保证线程安全的?
  4. 为什么HashTable、Collections.synchronizedMap()没人提了 , ConcurrentHashMap却满天飞、逢面必问呢?
  5. 面试官问你ConcurrentHashMap真正要考察的知识是什么?
那接下来就让我们一起带着问题去学习这些个为什么 。
本文源码基于JDK1.8 。
2、HashMap中的那些线程不安全的操作关于HashMap的实现原理 , 你必须熟记于心 , 然后我们才能继续去学习ConcurrentHashMap , 这里一张图来回顾一下HashMap的数据结构:
落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单HashMap底层数据结构
如果对HashMap底层实现原理不熟悉的可以先行阅读我的文章:
如果你这么去理解HashMap就会发现它真的很简单
接下来我们就进入HashMap的源码中来发现那些线程不安全的操作 。
3、线程不安全一:数组初始化当我们初始化一个HashMap的时候 , 他其实只是定义了initialCapacity和loadFactor这两个值 , 并没有初始化数组 , 而是懒加载的思想 , 在第一次put时候通过resize()方法去加载 。
落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单HashMap.put()方法源码
进入resize()方法 , 前面一堆计算拿到要初始化数组的大小newCap , 最终执行new Node[newCap];来初始化数组:
落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单数组初始化
为什么线程不安全?
如果上面的这段话是被两个线程来执行会出现什么情况?
当线程T1和T2同时去put一个值执行putVal()的时候 , 会是什么情况?
if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;例如:
  1. T1线程的任务是初始化数组长度为16 , T2线程的任务是初始化数组长度为32 , 是不是就无法得到一个确定的数组了?