ConcurrentHashMap 在JDK8中进行了巨大改动,它摒弃了 Segment (锁段)的概念,而是利用 CAS 算法和 synchronized 关键字来实现。它沿用了与它同时期的 HashMap 版本的思想,底层依然由 数组+链表+红黑树的方式思想(JDK7与JDK8中 HashMap 的实现),但是为了做到并发,又增加了很多辅助的类,例如 TreeBinTraverser 等对象内部类。

jdk1.8concurrenthashmap.png

put 方法

源码

  1. public V put(K key, V value) {
  2. return putVal(key, value, false);
  3. }
  4. final V putVal(K key, V value, boolean onlyIfAbsent) {
  5. // 不允许 key或value为null
  6. if (key == null || value == null) throw new NullPointerException();
  7. // 计算hash值
  8. int hash = spread(key.hashCode());
  9. int binCount = 0;
  10. // 死循环 何时插入成功 何时跳出
  11. for (Node<K,V>[] tab = table;;) {
  12. Node<K,V> f; int n, i, fh;
  13. //如果hash表为空的话,初始化hash表
  14. if (tab == null || (n = tab.length) == 0)
  15. tab = initTable();
  16. // /根据hash值计算出值在hash表里面的位置
  17. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  18. // 如果这个位置没有值 ,直接放进去,不需要加锁
  19. if (casTabAt(tab, i, null,
  20. new Node<K,V>(hash, key, value, null)))
  21. break; // no lock when adding to empty bin
  22. }
  23. // 当遇到表连接点时,进行扩容
  24. else if ((fh = f.hash) == MOVED)
  25. tab = helpTransfer(tab, f);
  26. else {
  27. V oldVal = null;
  28. //结点上锁 这里的结点可以理解为hash值相同组成的链表的头结点
  29. synchronized (f) {
  30. if (tabAt(tab, i) == f) {
  31. // fh〉0 说明这个节点是一个链表的节点 不是树的节点
  32. if (fh >= 0) {
  33. binCount = 1;
  34. // 在这里遍历链表所有的结点
  35. for (Node<K,V> e = f;; ++binCount) {
  36. K ek;
  37. // 如果hash值和key值相同 则修改对应结点的value值
  38. if (e.hash == hash &&
  39. ((ek = e.key) == key ||
  40. (ek != null && key.equals(ek)))) {
  41. oldVal = e.val;
  42. if (!onlyIfAbsent)
  43. e.val = value;
  44. break;
  45. }
  46. Node<K,V> pred = e;
  47. // 遍历到了最后一个结点,证明新的节点需要插入
  48. // 就把它插入在链表尾部
  49. if ((e = e.next) == null) {
  50. pred.next = new Node<K,V>(hash, key,
  51. value, null);
  52. break;
  53. }
  54. }
  55. }
  56. // 如果这个节点是树节点,就按照树的方式插入值
  57. else if (f instanceof TreeBin) {
  58. Node<K,V> p;
  59. binCount = 2;
  60. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
  61. value)) != null) {
  62. oldVal = p.val;
  63. if (!onlyIfAbsent)
  64. p.val = value;
  65. }
  66. }
  67. }
  68. }
  69. if (binCount != 0) {
  70. // 如果链表长度已经达到临界值8 就需要把链表转换为红黑树
  71. if (binCount >= TREEIFY_THRESHOLD)
  72. treeifyBin(tab, i);
  73. if (oldVal != null)
  74. return oldVal;
  75. break;
  76. }
  77. }
  78. }
  79. addCount(1L, binCount);
  80. return null;
  81. }

扩容

  1. final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  2. Node<K,V>[] nextTab; int sc;
  3. if (tab != null && (f instanceof ForwardingNode) &&
  4. (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
  5. int rs = resizeStamp(tab.length);
  6. while (nextTab == nextTable && table == tab &&
  7. (sc = sizeCtl) < 0) {
  8. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  9. sc == rs + MAX_RESIZERS || transferIndex <= 0)
  10. break;
  11. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
  12. transfer(tab, nextTab);
  13. break;
  14. }
  15. }
  16. return nextTab;
  17. }
  18. return table;
  19. }

get 方法

步骤

  1. 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  2. 如果是红黑树那就按照树的方式获取值。
  3. 就不满足那就按照链表的方式遍历获取值

源码

  1. public V get(Object key) {
  2. Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  3. int h = spread(key.hashCode());
  4. if ((tab = table) != null && (n = tab.length) > 0 &&
  5. (e = tabAt(tab, (n - 1) & h)) != null) {
  6. if ((eh = e.hash) == h) {
  7. if ((ek = e.key) == key || (ek != null && key.equals(ek)))
  8. return e.val;
  9. }
  10. else if (eh < 0)
  11. return (p = e.find(h, key)) != null ? p.val : null;
  12. while ((e = e.next) != null) {
  13. if (e.hash == h &&
  14. ((ek = e.key) == key || (ek != null && key.equals(ek))))
  15. return e.val;
  16. }
  17. }
  18. return null;
  19. }

参考