1、ConcurrentHashMap-底层数据结构

  1. 1ConcurrentHashMap-底层数据结构:
  2. 1-1jdk1.7: 采用Segment + HashEntry的方式进行实现,采取分段锁来保证安全性。
  3. 1-1-1Segment 的结构和 HashMap 类似,是一个数组和链表结构,继承了ReentrantLock类,是 ReentrantLock 重入锁,每个Segment 对应一把锁。
  4. 1-1-2Segment的优点:如果多个线程访问不同的Segment,实际是没有冲突的 ,这与jdk8是类似的(jdk8是把锁加在每个链表/红黑树的头结点对象上,jdk7是把锁加在每个Segment对象上)
  5. 1-1-3、缺点:Segment 数组默认大小为16,这个容量初始化之后就不能改变了,并且不是懒惰初始化的。
  6. 1-1-4HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 里包含一个 HashEntry 数组。
  7. 1-1-5、源码:
  8. final Segment<K,V>[] segments;
  9. static final class HashEntry<K,V> {
  10. final K key;
  11. final int hash;
  12. volatile V value;
  13. final HashEntry<K,V> next;
  14. }
  15. 1-2jdk1.8: 摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 Synchronized CAS 来操作。
  16. 1-2-1、源码:
  17. transient volatile Node<K,V>[] table;

1-1、ConcurrentHashMap-底层数据结构-jdk1.7-示意图


容器(Map)-ConcurrentHashMap-源码分析 - 图1

1-2、ConcurrentHashMap-底层数据结构-jdk1.8-示意图

image.png

1-3、ConcurrentHashMap-底层数据结构-jdk1.8-基本属性

  1. 1ConcurrentHashMap-jdk1.8-基本属性:
  2. //node数组最大容量:1 << 30 = 1*2^30=1073741824
  3. private static final int MAXIMUM_CAPACITY = 1 << 30;
  4. //默认初始值,必须是2的幂数
  5. private static final int DEFAULT_CAPACITY = 16;
  6. //数组可能最大值,需要与toArray()相关方法关联
  7. static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  8. //并发级别,遗留下来的,为兼容以前的版本
  9. private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  10. //负载因子
  11. private static final float LOAD_FACTOR = 0.75f;
  12. //链表转红黑树阀值,> 8 链表转换为红黑树
  13. static final int TREEIFY_THRESHOLD = 8;
  14. //树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
  15. static final int UNTREEIFY_THRESHOLD = 6;
  16. //转化为红黑树的表的最小容量
  17. static final int MIN_TREEIFY_CAPACITY = 64;
  18. //每次进行转移的最小值
  19. private static final int MIN_TRANSFER_STRIDE = 16;
  20. //生成sizeCtl所使用的bit位数
  21. private static int RESIZE_STAMP_BITS = 16;
  22. //2^15-1,help resize的最大线程数
  23. private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
  24. //32-16=16,sizeCtl中记录size大小的偏移量
  25. private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
  26. //forwarding nodes的hash值
  27. static final int MOVED = -1;
  28. //树根节点的hash值
  29. static final int TREEBIN = -2;
  30. //ReservationNode的hash值
  31. static final int RESERVED = -3;
  32. //可用处理器数量
  33. static final int NCPU = Runtime.getRuntime().availableProcessors();
  34. //存放node的数组
  35. transient volatile Node<K,V>[] table;
  36. /*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
  37. *当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
  38. *当为0时:代表当时的table还没有被初始化
  39. *当为正数时:表示初始化或者下一次进行扩容的大小
  40. */
  41. private transient volatile int sizeCtl;
  42. // 下一个表,过渡表,用于在扩容时将table表中的结点转移到nextTable中。
  43. private transient volatile Node<K,V>[] nextTable;
  44. // 基本计数
  45. private transient volatile long baseCount;
  46. // 扩容下另一个表的索引
  47. private transient volatile int transferIndex;
  48. // 旋转锁
  49. private transient volatile int cellsBusy;
  50. // counterCell表
  51. private transient volatile CounterCell[] counterCells;

1-4、ConcurrentHashMap-底层数据结构-jdk1.8-关键类

  1. 1ConcurrentHashMap-jdk1.8-关键类:
  2. 1-1Node,静态内部类,保存 keyvalue key hash 值的数据结构。
  3. //它对value和next属性设置了volatile同步锁(与JDK7的Segment相同),它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。
  4. static class Node<K,V> implements Map.Entry<K,V> {
  5. final int hash;
  6. final K key;
  7. volatile V val;
  8. volatile Node<K,V> next;
  9. Node(int hash, K key, V val, Node<K,V> next) {
  10. this.hash = hash;
  11. this.key = key;
  12. this.val = val;
  13. this.next = next;
  14. }
  15. public final K getKey() { return key; }
  16. public final V getValue() { return val; }
  17. public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
  18. public final String toString(){ return key + "=" + val; }
  19. public final V setValue(V value) {
  20. throw new UnsupportedOperationException();
  21. }
  22. public final boolean equals(Object o) {
  23. Object k, v, u; Map.Entry<?,?> e;
  24. return ((o instanceof Map.Entry) &&
  25. (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
  26. (v = e.getValue()) != null &&
  27. (k == key || k.equals(key)) &&
  28. (v == (u = val) || v.equals(u)));
  29. }
  30. /**
  31. * Virtualized support for map.get(); overridden in subclasses.
  32. */
  33. Node<K,V> find(int h, Object k) {
  34. Node<K,V> e = this;
  35. if (k != null) {
  36. do {
  37. K ek;
  38. if (e.hash == h &&
  39. ((ek = e.key) == k || (ek != null && k.equals(ek))))
  40. return e;
  41. } while ((e = e.next) != null);
  42. }
  43. return null;
  44. }
  45. }
  46. 1-2TreeNode,静态内部类,
  47. /**
  48. 当链表长度过长的时候,会转换为TreeNode。它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类。
  49. */
  50. static final class TreeNode<K,V> extends Node<K,V> {
  51. TreeNode<K,V> parent; // red-black tree links
  52. TreeNode<K,V> left;
  53. TreeNode<K,V> right;
  54. TreeNode<K,V> prev; // needed to unlink next upon deletion
  55. boolean red;
  56. //TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问
  57. TreeNode(int hash, K key, V val, Node<K,V> next,TreeNode<K,V> parent) {
  58. super(hash, key, val, next);
  59. this.parent = parent;
  60. }
  61. Node<K,V> find(int h, Object k) {
  62. return findTreeNode(h, k, null);
  63. }
  64. /**
  65. * Returns the TreeNode (or null if not found) for the given key
  66. * starting at given root.
  67. */
  68. final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
  69. if (k != null) {
  70. TreeNode<K,V> p = this;
  71. do {
  72. int ph, dir; K pk; TreeNode<K,V> q;
  73. TreeNode<K,V> pl = p.left, pr = p.right;
  74. if ((ph = p.hash) > h)
  75. p = pl;
  76. else if (ph < h)
  77. p = pr;
  78. else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
  79. return p;
  80. else if (pl == null)
  81. p = pr;
  82. else if (pr == null)
  83. p = pl;
  84. else if ((kc != null ||
  85. (kc = comparableClassFor(k)) != null) &&
  86. (dir = compareComparables(kc, k, pk)) != 0)
  87. p = (dir < 0) ? pl : pr;
  88. else if ((q = pr.findTreeNode(h, k, kc)) != null)
  89. return q;
  90. else
  91. p = pl;
  92. } while (p != null);
  93. }
  94. return null;
  95. }
  96. }
  97. 1-3TreeBin类,静态内部类,
  98. /**
  99. 并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点.
  100. 在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象.
  101. 这个类还带有了读写锁。
  102. */
  103. static final class TreeBin<K,V> extends Node<K,V> {
  104. TreeNode<K,V> root;
  105. volatile TreeNode<K,V> first;
  106. volatile Thread waiter;
  107. volatile int lockState;
  108. // values for lockState
  109. static final int WRITER = 1; // set while holding write lock
  110. static final int WAITER = 2; // set when waiting for write lock
  111. static final int READER = 4; // increment value for setting read lock
  112. /**
  113. * Tie-breaking utility for ordering insertions when equal
  114. * hashCodes and non-comparable. We don't require a total
  115. * order, just a consistent insertion rule to maintain
  116. * equivalence across rebalancings. Tie-breaking further than
  117. * necessary simplifies testing a bit.
  118. */
  119. static int tieBreakOrder(Object a, Object b) {
  120. int d;
  121. if (a == null || b == null ||
  122. (d = a.getClass().getName().
  123. compareTo(b.getClass().getName())) == 0)
  124. d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
  125. -1 : 1);
  126. return d;
  127. }
  128. /**
  129. * Creates bin with initial set of nodes headed by b.
  130. */
  131. TreeBin(TreeNode<K,V> b) {
  132. //指定了它的hash值为TREEBIN常量
  133. super(TREEBIN, null, null, null);
  134. this.first = b;
  135. TreeNode<K,V> r = null;
  136. for (TreeNode<K,V> x = b, next; x != null; x = next) {
  137. next = (TreeNode<K,V>)x.next;
  138. x.left = x.right = null;
  139. if (r == null) {
  140. x.parent = null;
  141. x.red = false;
  142. r = x;
  143. }
  144. else {
  145. K k = x.key;
  146. int h = x.hash;
  147. Class<?> kc = null;
  148. for (TreeNode<K,V> p = r;;) {
  149. int dir, ph;
  150. K pk = p.key;
  151. if ((ph = p.hash) > h)
  152. dir = -1;
  153. else if (ph < h)
  154. dir = 1;
  155. else if ((kc == null &&
  156. (kc = comparableClassFor(k)) == null) ||
  157. (dir = compareComparables(kc, k, pk)) == 0)
  158. dir = tieBreakOrder(k, pk);
  159. TreeNode<K,V> xp = p;
  160. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  161. x.parent = xp;
  162. if (dir <= 0)
  163. xp.left = x;
  164. else
  165. xp.right = x;
  166. r = balanceInsertion(r, x);
  167. break;
  168. }
  169. }
  170. }
  171. }
  172. this.root = r;
  173. assert checkInvariants(root);
  174. }
  175. //........
  176. }
  177. 1-4ForwardingNode类,静态内部类,
  178. /**
  179. 一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1。
  180. find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。
  181. */
  182. static final class ForwardingNode<K,V> extends Node<K,V> {
  183. final Node<K,V>[] nextTable;
  184. ForwardingNode(Node<K,V>[] tab) {
  185. super(MOVED, null, null, null);
  186. this.nextTable = tab;
  187. }
  188. Node<K,V> find(int h, Object k) {
  189. // loop to avoid arbitrarily deep recursion on forwarding nodes
  190. outer: for (Node<K,V>[] tab = nextTable;;) {
  191. Node<K,V> e; int n;
  192. if (k == null || tab == null || (n = tab.length) == 0 ||
  193. (e = tabAt(tab, (n - 1) & h)) == null)
  194. return null;
  195. for (;;) {
  196. int eh; K ek;
  197. if ((eh = e.hash) == h &&
  198. ((ek = e.key) == k || (ek != null && k.equals(ek))))
  199. return e;
  200. if (eh < 0) {
  201. if (e instanceof ForwardingNode) {
  202. tab = ((ForwardingNode<K,V>)e).nextTable;
  203. continue outer;
  204. }
  205. else
  206. return e.find(h, k);
  207. }
  208. if ((e = e.next) == null)
  209. return null;
  210. }
  211. }
  212. }
  213. }
  214. 1-5Traverser
  215.    Traverser类主要用于遍历操作,其子类有BaseIteratorKeySpliteratorValueSpliteratorEntrySpliterator四个类,BaseIterator用于遍历操作。KeySplitertorValueSpliteratorEntrySpliterator则用于键、值、键值对的划分。
  216.   1-6CollectionView
  217. CollectionView抽象类主要定义了视图操作,其子类KeySetViewValueSetViewEntrySetView分别表示键视图、值视图、键值对视图。对视图均可以进行操作。
  218.   1-7Segment
  219.    Segment类在JDK1.8中与之前的版本的JDK作用存在很大的差别,JDK1.8下,其在普通的ConcurrentHashMap操作中已经没有失效,其在序列化与反序列化的时候会发挥作用。
  220.   1-8CounterCell
  221.    CounterCell类主要用于对baseCount的计数。

2、ConcurrentHashMap-Unsafe|CAS

  1. 1Unsafe|CAS
  2. 1-1ConcurrentHashMap类中,U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。
  3. 1-2ConcurrentHashMap类中,unsafe静态块,控制了一些属性的修改工作,比如最常用的SIZECTL
  4. 1-3ConcurrentHashMap类中,添加了Unsafe实例,主要用于反射获取对象相应的字段。

3、ConcurrentHashMap-构造方法

  1. 1ConcurrentHashMap-构造方法:
  2. //无参构造函数用于创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
  3. /**
  4. concurrencyLevel:
  5. 1.表示并发级别,这个值用来确定Segment的个数,Segment的个数是大于等于concurrencyLevel的第一个2的n次方的数。
  6. 2.比如,如果concurrencyLevel为12,13,14,15,16这些数,则Segment的数目为16(2的4次方)。默认值为static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  7. 3.也就是Segment的个数不能超过规定的最大Segment的个数,默认值为static final int MAX_SEGMENTS = 1 << 16;,如果超过这个值,设置为这个值。
  8. */
  9. public ConcurrentHashMap() {
  10. }
  11. //构造函数用于创建一个带有指定初始容量、默认加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
  12. public ConcurrentHashMap(int initialCapacity) {
  13. if (initialCapacity < 0) // 初始容量小于0,抛出异常
  14. throw new IllegalArgumentException();
  15. int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
  16. MAXIMUM_CAPACITY :
  17. tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));// 找到最接近该容量的2的幂次方数
  18. // 初始化
  19. this.sizeCtl = cap;
  20. }
  21. //该构造函数用于创建一个带有指定初始容量、加载因子和默认 concurrencyLevel (1) 的新的空映射。
  22. public ConcurrentHashMap(int initialCapacity, float loadFactor) {
  23. this(initialCapacity, loadFactor, 1);
  24. }
  25. //该构造函数用于创建一个带有指定初始容量、加载因子和并发级别的新的空映射。
  26. public ConcurrentHashMap(int initialCapacity,
  27. float loadFactor, int concurrencyLevel) {
  28. if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
  29. throw new IllegalArgumentException();
  30. if (initialCapacity < concurrencyLevel) // Use at least as many bins
  31. initialCapacity = concurrencyLevel; // as estimated threads
  32. long size = (long)(1.0 + (long)initialCapacity / loadFactor);
  33. int cap = (size >= (long)MAXIMUM_CAPACITY) ?
  34. MAXIMUM_CAPACITY : tableSizeFor((int)size);
  35. this.sizeCtl = cap;
  36. }
  37. //该构造函数用于构造一个与给定映射具有相同映射关系的新映射。
  38. public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
  39. this.sizeCtl = DEFAULT_CAPACITY;
  40. putAll(m);
  41. }
  42. 2、构造函数-小结:
  43. 2-1、构造函数而言,会根据输入的initialCapacity的大小来确定一个最小的且大于等于initialCapacity大小的2n次幂,如initialCapacity15,则sizeCtl16,若initialCapacity16,则sizeCtl16。若initialCapacity大小超过了允许的最大值,则sizeCtl为最大值。
  44. 2-2、值得注意的是,构造函数中的concurrencyLevel参数已经在JDK1.8中的意义发生了很大的变化,其并不代表所允许的并发数,其只是用来确定sizeCtl大小,在JDK1.8中的并发控制都是针对具体的桶而言,即有多少个桶就可以允许多少个并发数。

4、ConcurrentHashMap-添加元素

  1. 1、添加元素:.put()方法,采用 CAS + synchronized 实现并发插入或更新操作。
  2. 2、源代码:
  3. //.put()方法,底层调用了.putVal()进行数据的插入
  4. public V put(K key, V value) {
  5. return putVal(key, value, false);
  6. }
  7. final V putVal(K key, V value, boolean onlyIfAbsent) {
  8. //键或值为空,抛出异常
  9. if (key == null || value == null)
  10. throw new NullPointerException();
  11. // 键的hash值经过计算获得hash值
  12. int hash = spread(key.hashCode());
  13. int binCount = 0;
  14. for (Node<K,V>[] tab = table;;) { // 无限循环,何时插入成功 何时跳出
  15. Node<K,V> f; int n, i, fh;
  16. // 表为空或者表的长度为0
  17. if (tab == null || (n = tab.length) == 0)
  18. tab = initTable();// 初始化表
  19. // 表不为空并且表的长度大于0,并且该桶不为空
  20. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  21. // 比较并且交换值,如tab的第i项为空则用新生成的node替换
  22. if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))
  23. break; // no lock when adding to empty bin
  24. }
  25. // 该结点的hash值为MOVED, 为表连接点时,需要进行整合表的操作
  26. else if ((fh = f.hash) == MOVED)
  27. tab = helpTransfer(tab, f); // 进行结点的转移(在扩容的过程中)
  28. else {
  29. V oldVal = null;
  30. // 加锁同步
  31. synchronized (f) {
  32. // 找到table表下标为i的节点
  33. if (tabAt(tab, i) == f) {
  34. // 该table表中该结点的hash值大于0
  35. if (fh >= 0) {
  36. // binCount赋值为1
  37. binCount = 1;
  38. // 无限循环
  39. for (Node<K,V> e = f;; ++binCount) {
  40. K ek;
  41. // 结点的hash值相等并且key也相等
  42. if (e.hash == hash &&
  43. ((ek = e.key) == key ||
  44. (ek != null && key.equals(ek)))) {
  45. // 保存该结点的val值
  46. oldVal = e.val;
  47. // 进行判断
  48. if (!onlyIfAbsent)
  49. // 将指定的value保存至节点,即进行了节点值的更新
  50. e.val = value;
  51. break;
  52. }
  53. // 保存当前节点
  54. Node<K,V> pred = e;
  55. // 当前结点的下一个结点为空,即为最后一个结点
  56. if ((e = e.next) == null) {
  57. // 新生一个结点并且赋值给next域
  58. pred.next = new Node<K,V>(hash, key, value, null);
  59. break;
  60. }
  61. }
  62. }
  63. // 节点为红黑树结点类型
  64. else if (f instanceof TreeBin) {
  65. Node<K,V> p;
  66. // binCount赋值为2
  67. binCount = 2;
  68. // 将hash、key、value放入红黑树
  69. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
  70. // 保存结点的val
  71. oldVal = p.val;
  72. if (!onlyIfAbsent)
  73. // 赋值结点value值
  74. p.val = value;
  75. }
  76. }
  77. }
  78. }
  79. // binCount不为0
  80. if (binCount != 0) {
  81. // 如果binCount大于等于转化为红黑树的阈值
  82. if (binCount >= TREEIFY_THRESHOLD)
  83. // 进行转化
  84. treeifyBin(tab, i);
  85. if (oldVal != null) // 旧值不为空
  86. // 返回旧值
  87. return oldVal;
  88. break;
  89. }
  90. }
  91. }
  92. // 增加binCount的数量
  93. addCount(1L, binCount);
  94. return null;
  95. }
  96. 2-1、.putVal()方法,执行流程:
  97. 2-1-1、判断存储的keyvalue是否为空,若为空,则抛出异常,否则,进入步骤2-1-2
  98. 2-1-2、计算keyhash值,随后进入无限循环,该无限循环可以确保成功插入数据,若table表为空或者长度为0,则初始化table表,否则,进入步骤2-1-3
  99. 2-1-3、根据keyhash值取出table表中的结点元素,若取出的结点为空(该桶为空),则使用CASkeyvaluehash值生成的结点放入桶中。否则,进入步骤2-1-4
  100. 2-1-4、若该结点的的hash值为MOVED,则对该桶中的结点进行转移,否则,进入步骤2-1-5
  101. 2-1-5、对桶中的第一个结点(即table表中的结点)进行加锁,对该桶进行遍历,桶中的结点的hash值与key值与给定的hash值和key值相等,则根据标识选择是否进行更新操作(用给定的value值替换该结点的value值),若遍历完桶仍没有找到hash值与key值和指定的hash值与key值相等的结点,则直接新生一个结点并赋值为之前最后一个结点的下一个结点。进入步骤2-1-6
  102. 2-1-6、若binCount值达到红黑树转化的阈值,则将桶中的结构转化为红黑树存储,最后,增加binCount的值。
  103. 2-2、初始化表.initTable()方法源码分析:
  104. //对于table的大小,会根据sizeCtl的值进行设置,如果没有设置szieCtl的值,那么默认生成的table大小为16,否则,会根据sizeCtl的大小设置table大小。
  105. private final Node<K,V>[] initTable() {
  106. Node<K,V>[] tab; int sc;
  107. while ((tab = table) == null || tab.length == 0) { // 表为空或者表的长度为0
  108. // 如果这个值〈0,表示其他线程正在进行初始化,就放弃这个操作。进行线程让步等待
  109. if ((sc = sizeCtl) < 0)
  110. Thread.yield(); // lost initialization race; just spin
  111. else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 比较sizeCtl的值与sc是否相等,相等则用-1替换,防止其他线程进入。
  112. try {
  113. if ((tab = table) == null || tab.length == 0) { // table表为空或者大小为0
  114. // sc的值是否大于0,若是,则n为sc,否则,n为默认初始容量
  115. int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
  116. @SuppressWarnings("unchecked")
  117. // 新生结点数组
  118. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
  119. // 赋值给table
  120. table = tab = nt;
  121. // sc为n * 3/4==>n-(n/2)/2=3/4*n=0.75*n
  122. sc = n - (n >>> 2);
  123. }
  124. } finally {
  125. // 设置sizeCtl的值,初始化数组后,将sizeCtl的值改为0.75*n。
  126. sizeCtl = sc;
  127. }
  128. break;
  129. }
  130. }
  131. // 返回table表
  132. return tab;
  133. }
  134. 2-3、.tabAt()方法源码分析:原子操作。
  135. //原子操作,用于对指定位置的节点进行操作。
  136. //获得在i位置上的Node节点,即返回table数组中下标为i的结点
  137. static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
  138. //getObjectVolatile的第二项参数为下标为i的偏移地址。
  139. //U.getObjectVolatile(),直接获取指定内存的数据,保证了每次拿到数据都是最新的。
  140. return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
  141. }
  142. 2-4、.casTabAt()方法源码分析:原子操作。
  143. /**
  144. 利用CAS算法设置i位置上的Node节点。之所以能实现并发是因为他指定了原来这个节点的值是多少
  145. 在CAS算法中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受你的修改,否则拒绝你的修改
  146. 因此当前线程中的值并不是最新的值,这种修改可能会覆盖掉其他线程的修改结果.有点类似于SVN
  147. */
  148. //用于比较table数组下标为i的结点是否为c,若为c,则用v交换操作。否则,不进行交换操作。
  149. static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
  150. return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
  151. }
  152. 2-5、.setTabAt()方法源码分析:原子操作
  153. //利用volatile方法设置节点位置的值
  154. static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
  155. U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
  156. }
  157. 2-6、.helpTransfer()方法源码分析:
  158. //用于在扩容时将table表中的结点转移到nextTable中。
  159. //是一个协助扩容的方法。这个方法被调用的时候,当前ConcurrentHashMap一定已经有了nextTable对象,首先拿到这个nextTable对象,调用transfer方法。回看上面的transfer方法可以看到,当本线程进入扩容方法的时候会直接进入复制阶段。
  160. final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  161. Node<K,V>[] nextTab; int sc;
  162. if (tab != null && (f instanceof ForwardingNode) &&
  163. (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { // table表不为空并且结点类型使ForwardingNode类型,并且结点的nextTable不为空
  164. int rs = resizeStamp(tab.length);
  165. while (nextTab == nextTable && table == tab &&
  166. (sc = sizeCtl) < 0) { // 条件判断
  167. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  168. sc == rs + MAX_RESIZERS || transferIndex <= 0) //
  169. break;
  170. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { // 比较并交换
  171. // 将table的结点转移到nextTab中,具体见扩容源码章节
  172. transfer(tab, nextTab);
  173. break;
  174. }
  175. }
  176. return nextTab;
  177. }
  178. return table;
  179. }
  180. 2-7、.putTreeVal()方法源码分析:
  181. //该方法用于将指定的hash、key、value值添加到红黑树中,若已经添加了,则返回null,否则返回该结点。
  182. final TreeNode<K,V> putTreeVal(int h, K k, V v) {
  183. Class<?> kc = null;
  184. boolean searched = false;
  185. for (TreeNode<K,V> p = root;;) {
  186. int dir, ph; K pk;
  187. if (p == null) {
  188. first = root = new TreeNode<K,V>(h, k, v, null, null);
  189. break;
  190. }
  191. else if ((ph = p.hash) > h)
  192. dir = -1;
  193. else if (ph < h)
  194. dir = 1;
  195. else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
  196. return p;
  197. else if ((kc == null &&
  198. (kc = comparableClassFor(k)) == null) ||
  199. (dir = compareComparables(kc, k, pk)) == 0) {
  200. if (!searched) {
  201. TreeNode<K,V> q, ch;
  202. searched = true;
  203. if (((ch = p.left) != null &&
  204. (q = ch.findTreeNode(h, k, kc)) != null) ||
  205. ((ch = p.right) != null &&
  206. (q = ch.findTreeNode(h, k, kc)) != null))
  207. return q;
  208. }
  209. dir = tieBreakOrder(k, pk);
  210. }
  211. TreeNode<K,V> xp = p;
  212. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  213. TreeNode<K,V> x, f = first;
  214. first = x = new TreeNode<K,V>(h, k, v, f, xp);
  215. if (f != null)
  216. f.prev = x;
  217. if (dir <= 0)
  218. xp.left = x;
  219. else
  220. xp.right = x;
  221. if (!xp.red)
  222. x.red = true;
  223. else {
  224. lockRoot();
  225. try {
  226. root = balanceInsertion(root, x);
  227. } finally {
  228. unlockRoot();
  229. }
  230. }
  231. break;
  232. }
  233. }
  234. assert checkInvariants(root);
  235. return null;
  236. }
  237. 2-8、.treeifyBin()方法源码分析:
  238. //用于将桶中的数据结构转化为红黑树,其中,值得注意的是,当table的长度未达到阈值时,会进行一次扩容操作,该操作会使得触发treeifyBin操作的某个桶中的所有元素进行一次重新分配,这样可以避免某个桶中的结点数量太大。
  239. private final void treeifyBin(Node<K,V>[] tab, int index) {
  240. Node<K,V> b; int n, sc;
  241. if (tab != null) { // 表不为空
  242. if ((n = tab.length) < MIN_TREEIFY_CAPACITY) // table表的长度小于最小的长度
  243. // 进行扩容,调整某个桶中结点数量过多的问题(由于某个桶中结点数量超出了阈值,则触发treeifyBin)
  244. tryPresize(n << 1);
  245. else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { // 桶中存在结点并且结点的hash值大于等于0
  246. synchronized (b) { // 对桶中第一个结点进行加锁
  247. if (tabAt(tab, index) == b) { // 第一个结点没有变化
  248. TreeNode<K,V> hd = null, tl = null;
  249. for (Node<K,V> e = b; e != null; e = e.next) { // 遍历桶中所有结点
  250. // 新生一个TreeNode结点
  251. TreeNode<K,V> p =
  252. new TreeNode<K,V>(e.hash, e.key, e.val,
  253. null, null);
  254. if ((p.prev = tl) == null) // 该结点前驱为空
  255. // 设置p为头结点
  256. hd = p;
  257. else
  258. // 尾节点的next域赋值为p
  259. tl.next = p;
  260. // 尾节点赋值为p
  261. tl = p;
  262. }
  263. // 设置table表中下标为index的值为hd
  264. setTabAt(tab, index, new TreeBin<K,V>(hd));
  265. }
  266. }
  267. }
  268. }
  269. }
  270. 2-9、.addCount()方法源码分析:
  271. //该方法主要完成binCount的值加1的操作。
  272. private final void addCount(long x, int check) {
  273. CounterCell[] as; long b, s;
  274. if ((as = counterCells) != null ||
  275. !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { // counterCells不为空或者比较交换失败
  276. CounterCell a; long v; int m;
  277. // 无竞争标识
  278. boolean uncontended = true;
  279. if (as == null || (m = as.length - 1) < 0 ||
  280. (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
  281. !(uncontended =
  282. U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { //
  283. fullAddCount(x, uncontended);
  284. return;
  285. }
  286. if (check <= 1)
  287. return;
  288. s = sumCount();
  289. }
  290. //.......
  291. }

5、ConcurrentHashMap-获取元素

  1. 1、获取元素:.get()方法
  2. 2、源代码:
  3. //get函数根据key的hash值来计算在哪个桶中,再遍历桶,查找元素,若找到则返回该结点,否则,返回null。
  4. public V get(Object key) {
  5. Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  6. // 计算key的hash值
  7. int h = spread(key.hashCode());
  8. if ((tab = table) != null && (n = tab.length) > 0 &&
  9. (e = tabAt(tab, (n - 1) & h)) != null) { // 表不为空并且表的长度大于0并且key所在的桶不为空
  10. if ((eh = e.hash) == h) { // 表中的元素的hash值与key的hash值相等
  11. if ((ek = e.key) == key || (ek != null && key.equals(ek))) // 键相等
  12. // 返回值
  13. return e.val;
  14. }
  15. else if (eh < 0) // 结点hash值小于0
  16. // 在桶(链表/红黑树)中查找
  17. return (p = e.find(h, key)) != null ? p.val : null;
  18. while ((e = e.next) != null) { // 对于结点hash值大于0的情况
  19. if (e.hash == h &&
  20. ((ek = e.key) == key || (ek != null && key.equals(ek))))
  21. return e.val;
  22. }
  23. }
  24. return null;
  25. }

6、ConcurrentHashMap-删除元素

  1. 1、删除元素:.remove()方法,内部调用了.replaceNode()方法
  2. 2、源代码:
  3. public V remove(Object key) {
  4. return replaceNode(key, null, null);
  5. }
  6. //对remove函数提供支持,remove函数底层是调用的replaceNode函数实现结点的删除。
  7. final V replaceNode(Object key, V value, Object cv) {
  8. // 计算key的hash值
  9. int hash = spread(key.hashCode());
  10. for (Node<K,V>[] tab = table;;) { // 无限循环
  11. Node<K,V> f; int n, i, fh;
  12. if (tab == null || (n = tab.length) == 0 ||
  13. (f = tabAt(tab, i = (n - 1) & hash)) == null) // table表为空或者表长度为0或者key所对应的桶为空
  14. // 跳出循环
  15. break;
  16. else if ((fh = f.hash) == MOVED) // 桶中第一个结点的hash值为MOVED
  17. // 转移
  18. tab = helpTransfer(tab, f);
  19. else {
  20. V oldVal = null;
  21. boolean validated = false;
  22. synchronized (f) { // 加锁同步
  23. if (tabAt(tab, i) == f) { // 桶中的第一个结点没有发生变化
  24. if (fh >= 0) { // 结点hash值大于0
  25. validated = true;
  26. for (Node<K,V> e = f, pred = null;;) { // 无限循环
  27. K ek;
  28. if (e.hash == hash &&
  29. ((ek = e.key) == key ||
  30. (ek != null && key.equals(ek)))) { // 结点的hash值与指定的hash值相等,并且key也相等
  31. V ev = e.val;
  32. if (cv == null || cv == ev ||
  33. (ev != null && cv.equals(ev))) { // cv为空或者与结点value相等或者不为空并且相等
  34. // 保存该结点的val值
  35. oldVal = ev;
  36. if (value != null) // value为null
  37. // 设置结点value值
  38. e.val = value;
  39. else if (pred != null) // 前驱不为空
  40. // 前驱的后继为e的后继,即删除了e结点
  41. pred.next = e.next;
  42. else
  43. // 设置table表中下标为index的值为e.next
  44. setTabAt(tab, i, e.next);
  45. }
  46. break;
  47. }
  48. pred = e;
  49. if ((e = e.next) == null)
  50. break;
  51. }
  52. }
  53. else if (f instanceof TreeBin) { // 为红黑树结点类型
  54. validated = true;
  55. // 类型转化
  56. TreeBin<K,V> t = (TreeBin<K,V>)f;
  57. TreeNode<K,V> r, p;
  58. if ((r = t.root) != null &&
  59. (p = r.findTreeNode(hash, key, null)) != null) { // 根节点不为空并且存在与指定hash和key相等的结点
  60. // 保存p结点的value
  61. V pv = p.val;
  62. if (cv == null || cv == pv ||
  63. (pv != null && cv.equals(pv))) { // cv为空或者与结点value相等或者不为空并且相等
  64. oldVal = pv;
  65. if (value != null)
  66. p.val = value;
  67. else if (t.removeTreeNode(p)) // 移除p结点
  68. setTabAt(tab, i, untreeify(t.first));
  69. }
  70. }
  71. }
  72. }
  73. }
  74. if (validated) {
  75. if (oldVal != null) {
  76. if (value == null)
  77. // baseCount值减一
  78. addCount(-1L, -1);
  79. return oldVal;
  80. }
  81. break;
  82. }
  83. }
  84. }
  85. return null;
  86. }

7、ConcurrentHashMap-扩容

  1. 1、扩容操作:.tranfer()方法-它支持多线程进行扩容操作,而并没有加锁。
  2. 2、源代码:
  3. /**
  4. * 将每个bin中的节点移动和/或复制到新表中。
  5. * 它支持多线程进行扩容操作,而并没有加锁。
  6. */
  7. private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
  8. int n = tab.length, stride;
  9. if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
  10. stride = MIN_TRANSFER_STRIDE; // subdivide range
  11. if (nextTab == null) { // initiating
  12. try {
  13. @SuppressWarnings("unchecked")
  14. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//构造一个nextTable对象 它的容量是原来的两倍
  15. nextTab = nt;
  16. } catch (Throwable ex) { // try to cope with OOME
  17. sizeCtl = Integer.MAX_VALUE;
  18. return;
  19. }
  20. nextTable = nextTab;
  21. transferIndex = n;
  22. }
  23. int nextn = nextTab.length;
  24. ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//构造一个连节点指针 用于标志位
  25. boolean advance = true;//并发扩容的关键属性 如果等于true 说明这个节点已经处理过
  26. boolean finishing = false; // to ensure sweep before committing nextTab
  27. for (int i = 0, bound = 0;;) {
  28. Node<K,V> f; int fh;
  29. //这个while循环体的作用就是在控制i-- 通过i--可以依次遍历原hash表中的节点
  30. while (advance) {
  31. int nextIndex, nextBound;
  32. if (--i >= bound || finishing)
  33. advance = false;
  34. else if ((nextIndex = transferIndex) <= 0) {
  35. i = -1;
  36. advance = false;
  37. }
  38. else if (U.compareAndSwapInt
  39. (this, TRANSFERINDEX, nextIndex,
  40. nextBound = (nextIndex > stride ?
  41. nextIndex - stride : 0))) {
  42. bound = nextBound;
  43. i = nextIndex - 1;
  44. advance = false;
  45. }
  46. }
  47. if (i < 0 || i >= n || i + n >= nextn) {
  48. int sc;
  49. if (finishing) {
  50. //如果所有的节点都已经完成复制工作 就把nextTable赋值给table 清空临时对象nextTable
  51. nextTable = null;
  52. table = nextTab;
  53. sizeCtl = (n << 1) - (n >>> 1);//扩容阈值设置为原来容量的1.5倍 依然相当于现在容量的0.75倍
  54. return;
  55. }
  56. //利用CAS方法更新这个扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作
  57. if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
  58. if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
  59. return;
  60. finishing = advance = true;
  61. i = n; // recheck before commit
  62. }
  63. }
  64. //如果遍历到的节点为空 则放入ForwardingNode指针
  65. else if ((f = tabAt(tab, i)) == null)
  66. advance = casTabAt(tab, i, null, fwd);
  67. //如果遍历到ForwardingNode节点 说明这个点已经被处理过了 直接跳过 这里是控制并发扩容的核心:遍历到ForwardingNode节点,就向后继续遍历,再加上给节点上锁的机制,就完成了多线程的控制。
  68. else if ((fh = f.hash) == MOVED)
  69. advance = true; // already processed
  70. else {
  71. //节点上锁
  72. synchronized (f) {
  73. if (tabAt(tab, i) == f) {
  74. Node<K,V> ln, hn;
  75. //如果fh>=0 证明这是一个Node节点
  76. if (fh >= 0) {
  77. int runBit = fh & n;
  78. //以下的部分在完成的工作是构造两个链表 一个是原链表 另一个是原链表的反序排列
  79. Node<K,V> lastRun = f;
  80. for (Node<K,V> p = f.next; p != null; p = p.next) {
  81. int b = p.hash & n;
  82. if (b != runBit) {
  83. runBit = b;
  84. lastRun = p;
  85. }
  86. }
  87. if (runBit == 0) {
  88. ln = lastRun;
  89. hn = null;
  90. }
  91. else {
  92. hn = lastRun;
  93. ln = null;
  94. }
  95. for (Node<K,V> p = f; p != lastRun; p = p.next) {
  96. int ph = p.hash; K pk = p.key; V pv = p.val;
  97. if ((ph & n) == 0)
  98. ln = new Node<K,V>(ph, pk, pv, ln);
  99. else
  100. hn = new Node<K,V>(ph, pk, pv, hn);
  101. }
  102. //在nextTable的i位置上插入一个链表
  103. setTabAt(nextTab, i, ln);
  104. //在nextTable的i+n的位置上插入另一个链表
  105. setTabAt(nextTab, i + n, hn);
  106. //在table的i位置上插入forwardNode节点 表示已经处理过该节点
  107. setTabAt(tab, i, fwd);
  108. //设置advance为true 返回到上面的while循环中 就可以执行i--操作
  109. advance = true;
  110. }
  111. //对TreeBin对象进行处理 与上面的过程类似
  112. else if (f instanceof TreeBin) {
  113. TreeBin<K,V> t = (TreeBin<K,V>)f;
  114. TreeNode<K,V> lo = null, loTail = null;
  115. TreeNode<K,V> hi = null, hiTail = null;
  116. int lc = 0, hc = 0;
  117. //构造正序和反序两个链表
  118. for (Node<K,V> e = t.first; e != null; e = e.next) {
  119. int h = e.hash;
  120. TreeNode<K,V> p = new TreeNode<K,V>
  121. (h, e.key, e.val, null, null);
  122. if ((h & n) == 0) {
  123. if ((p.prev = loTail) == null)
  124. lo = p;
  125. else
  126. loTail.next = p;
  127. loTail = p;
  128. ++lc;
  129. }
  130. else {
  131. if ((p.prev = hiTail) == null)
  132. hi = p;
  133. else
  134. hiTail.next = p;
  135. hiTail = p;
  136. ++hc;
  137. }
  138. }
  139. //如果扩容后已经不再需要tree的结构 反向转换为链表结构
  140. ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
  141. (hc != 0) ? new TreeBin<K,V>(lo) : t;
  142. hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
  143. (lc != 0) ? new TreeBin<K,V>(hi) : t;
  144. //在nextTable的i位置上插入一个链表
  145. setTabAt(nextTab, i, ln);
  146. //在nextTable的i+n的位置上插入另一个链表
  147. setTabAt(nextTab, i + n, hn);
  148. //在table的i位置上插入forwardNode节点 表示已经处理过该节点
  149. setTabAt(tab, i, fwd);
  150. //设置advance为true 返回到上面的while循环中 就可以执行i--操作
  151. advance = true;
  152. }
  153. }
  154. }
  155. }
  156. }
  157. }
  158. 3、扩容分析-小结:
  159. 3-1、整个扩容操作分为两个部分:
  160. 3-1-1、是构建一个nextTable,它的容量是原来的两倍,这个操作是单线程完成的。这个单线程的保证是通过RESIZE_STAMP_SHIFT这个常量经过一次运算来保证的,这个地方在后面会有提到;
  161. 3-1-2、是将原来table中的元素复制到nextTable中,这里允许多线程进行操作。
  162. 3-2、扩容-单线程:遍历、复制的过程。首先根据运算得到需要遍历的次数i,然后利用tabAt方法获得i位置的元素。
  163. 3-2-1、如果这个位置为空,就在原table中的i位置放入forwardNode节点,这个也是触发并发扩容的关键点;
  164. 3-2-2、如果这个位置是Node节点(fh>=0),如果它是一个链表的头节点,就构造一个反序链表,把他们分别放在nextTableii+n的位置上。
  165. 3-2-3、如果这个位置是TreeBin节点(fh<0),也做一个反序处理,并且判断是否需要untreefi,把处理的结果分别放在nextTableii+n的位置上。
  166. 3-2-4、遍历过所有的节点以后就完成了复制工作,这时让nextTable作为新的table,并且更新sizeCtl为新容量的0.75 ,完成扩容。
  167. 3-3、扩容-多线程:
  168. 3-3-1、如果遍历到的节点是forward节点,就向后继续遍历,再加上给节点上锁的机制,就完成了多线程的控制。
  169. 3-3-2、多线程遍历节点,处理了一个节点,就把对应点的值setforward,另一个线程看到forward,就向后遍历。这样交叉就完成了复制工作。而且还很好的解决了线程安全的问题。

7-1、ConcurrentHashMap-扩容-多线程-逻辑图


image.png

  1. https://www.cnblogs.com/williamjie/p/9099861.html
  2. https://www.cnblogs.com/banjinbaijiu/p/9147434.html