initTable方法

    1. //Initializes table, using the size recorded in sizeCtl.
    2. private final Node<K,V>[] initTable() {
    3. Node<K,V>[] tab; int sc;
    4. while ((tab = table) == null || tab.length == 0) {
    5. if ((sc = sizeCtl) < 0)
    6. Thread.yield(); // lost initialization race; just spin
    7. else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
    8. try {
    9. if ((tab = table) == null || tab.length == 0) {
    10. int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
    11. @SuppressWarnings("unchecked")
    12. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
    13. table = tab = nt;
    14. //相当于0.75的负载系数 ,所以此时sc代表?
    15. sc = n - (n >>> 2);
    16. }
    17. } finally {
    18. sizeCtl = sc;
    19. }
    20. break;
    21. }
    22. }
    23. return tab;
    24. }

    里面需要注意的是变量 sizeCtl ,它的值决定着当前的初始化状态。 还要再理解下 -1 说明正在初始化 -N 说明有N-1个线程正在进行扩容 表示 table 初始化大小,如果 table 没有初始化 表示 table 容量 * 0.75,如果 table 已经初始化。

    put 方法:

    1. /** Implementation for put and putIfAbsent */
    2. final V putVal(K key, V value, boolean onlyIfAbsent) {
    3. if (key == null || value == null) throw new NullPointerException();
    4. int hash = spread(key.hashCode());
    5. int binCount = 0;
    6. //CAS经典写法,不成功无限重试,让再次进行循环进行相应操作。
    7. for (Node<K,V>[] tab = table;;) {
    8. // fh 后面存放目标位置的元素 hash 值(fh = f.hash))
    9. Node<K,V> f; int n, i, fh;
    10. if (tab == null || (n = tab.length) == 0)
    11. // 数组桶为空,初始化数组桶(自旋+CAS)
    12. tab = initTable();
    13. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    14. // 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出
    15. if (casTabAt(tab, i, null,
    16. new Node<K,V>(hash, key, value, null)))
    17. break; // no lock when adding to empty bin
    18. }
    19. else if ((fh = f.hash) == MOVED)
    20. //当前结点正在扩容,让当前线程调用helpTransfer也参与到扩容过程中来,扩容完毕后tab指向新table。
    21. tab = helpTransfer(tab, f);
    22. else {
    23. V oldVal = null;
    24. // 使用 synchronized 加锁加入节点
    25. synchronized (f) {
    26. if (tabAt(tab, i) == f) {
    27. // 说明是链表
    28. if (fh >= 0) {
    29. binCount = 1;
    30. // 循环加入新的或者覆盖节点
    31. for (Node<K,V> e = f;; ++binCount) {
    32. K ek;
    33. if (e.hash == hash &&
    34. ((ek = e.key) == key ||
    35. (ek != null && key.equals(ek)))) {
    36. oldVal = e.val;
    37. if (!onlyIfAbsent)
    38. e.val = value;
    39. break;
    40. }
    41. Node<K,V> pred = e;
    42. if ((e = e.next) == null) {
    43. pred.next = new Node<K,V>(hash, key,
    44. value, null);
    45. break;
    46. }
    47. }
    48. }
    49. else if (f instanceof TreeBin) {
    50. // 红黑树
    51. Node<K,V> p;
    52. binCount = 2;
    53. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
    54. value)) != null) {
    55. oldVal = p.val;
    56. if (!onlyIfAbsent)
    57. p.val = value;
    58. }
    59. }
    60. }
    61. }
    62. if (binCount != 0) {
    63. //如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树
    64. if (binCount >= TREEIFY_THRESHOLD)
    65. treeifyBin(tab, i);
    66. if (oldVal != null)
    67. return oldVal;
    68. break;
    69. }
    70. }
    71. }
    72. ////更新size,检测扩容
    73. addCount(1L, binCount);
    74. return null;
    75. }

    参考: 关于jdk1.8中ConcurrentHashMap的方方面面
    HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!


    Java7 中 ConcruuentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。

    Java8 中的 ConcruuentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。

    有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的锁升级