简介

由于HashMap是非线程安全的,而且HashTable和Collections.synchronizedMap()的效率很低(基本上是对读写操作加锁,一个线程在使用,其他线程必须等待)。因此可以使用并发安全的ConcurrentHashMap。

ConcurrentHashMap的实现原理和HashMap有很多相似之处,所以看了HashMap的源码后对于理解ConcurrentHashMap有很大的好处。

JDK1.7

ConcurrentHashMap采用 分段锁(Segments) 的机制,底层采用数组+链表的存储结构。

Segments继承了 ReentrantLock 用来充当锁的角色,每个Segment保护哈希表(table[])的若干个桶(HashBucket)。

JDK1.8

JDK1.8已经不使用分段锁机制来保证并发安全了,而是使用 CAS+Synchronized 来保证,底层使用数组+链表+红黑树的存储结构(类似于HashMap的改变)。

重要属性

以下是一些会用到的属性,部分在HashMap已经出现过。

其中比较重要的是 sizeCtl,这个标志控制了很多状态。

  1. / 最大容量
  2. private static final int MAXIMUM_CAPACITY = 1 << 30;
  3. // 默认初始容量
  4. private static final int DEFAULT_CAPACITY = 16;
  5. // 并发级别,主要是为了兼容之前的版本
  6. private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  7. // 负载因子
  8. private static final float LOAD_FACTOR = 0.75f;
  9. /**
  10. * The number of bits used for generation stamp in sizeCtl.
  11. * Must be at least 6 for 32bit arrays.
  12. */
  13. private static int RESIZE_STAMP_BITS = 16;
  14. /**
  15. * The maximum number of threads that can help resize.
  16. * Must fit in 32 - RESIZE_STAMP_BITS bits.
  17. */
  18. private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
  19. /**
  20. * The bit shift for recording size stamp in sizeCtl.
  21. */
  22. private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
  23. /*
  24. * Encodings for Node hash fields. See above for explanation.
  25. * 某些结点的hash值,在之后用这些去判断某个结点的类型
  26. */
  27. static final int MOVED = -1; // hash for forwarding nodes
  28. static final int TREEBIN = -2; // hash for roots of trees
  29. static final int RESERVED = -3; // hash for transient reservations
  30. static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
  31. // 哈希表数组,在第一次插入的时候才初始化,大小是2的幂,有volatile修饰
  32. transient volatile Node<K,V>[] table;
  33. // 扩容时使用,用来取代旧的table数组,有volatile修饰
  34. private transient volatile Node<K,V>[] nextTable;
  35. // 记录容器容量大小
  36. private transient volatile long baseCount;
  37. // -1是在初始化,-n表示有(n-1)个线程在扩容,等于0为默认值,大于0表示扩容阈值
  38. private transient volatile int sizeCtl;
  39. // 需要遍历的下标
  40. private transient volatile int transferIndex;
  41. /**
  42. * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
  43. */
  44. private transient volatile int cellsBusy;
  45. // 在高并发时候把对单个值的更新转化为数组上的更新,降低并发争用
  46. private transient volatile CounterCell[] counterCells;
  47. // views
  48. private transient KeySetView<K,V> keySet;
  49. private transient ValuesView<K,V> values;
  50. private transient EntrySetView<K,V> entrySet;

Node类

这里的Node类不允许直接setValue(),并且val和next使用了volatile修饰保证了可见性。

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash;
  3. final K key;
  4. // 使用volatile保证可见性
  5. volatile V val;
  6. volatile Node<K,V> next;
  7. Node(int hash, K key, V val, Node<K,V> next) {
  8. this.hash = hash;
  9. this.key = key;
  10. this.val = val;
  11. this.next = next;
  12. }
  13. public final K getKey() { return key; }
  14. public final V getValue() { return val; }
  15. public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
  16. public final String toString(){ return key + "=" + val; }
  17. // 不允许直接setValue
  18. public final V setValue(V value) {
  19. throw new UnsupportedOperationException();
  20. }
  21. public final boolean equals(Object o) {
  22. Object k, v, u; Map.Entry<?,?> e;
  23. return ((o instanceof Map.Entry) &&
  24. (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
  25. (v = e.getValue()) != null &&
  26. (k == key || k.equals(key)) &&
  27. (v == (u = val) || v.equals(u)));
  28. }
  29. // 辅助map.get()操作
  30. Node<K,V> find(int h, Object k) {
  31. Node<K,V> e = this;
  32. if (k != null) {
  33. do {
  34. K ek;
  35. if (e.hash == h &&
  36. ((ek = e.key) == k || (ek != null && k.equals(ek))))
  37. return e;
  38. } while ((e = e.next) != null);
  39. }
  40. return null;
  41. }
  42. }

ForwardingNode类

这个类对于ConcurrentHashMap很重要,是实现并发的核心之一。

这个类是用来标识table[]上的Node的,当表上的结点是 ForwardingNode 类时,说明这个结点已经被复制了,不需要再对这个结点进行操作了。

在后面很多方法中都有这个类的出现。

  1. // ForwardingNode类
  2. static final class ForwardingNode<K,V> extends Node<K,V> {
  3. final Node<K,V>[] nextTable;
  4. ForwardingNode(Node<K,V>[] tab) {
  5. // 标志结点hash值为MOVED(-1)
  6. super(MOVED, null, null, null);
  7. this.nextTable = tab;
  8. }
  9. // 从nextTable中查询结点
  10. Node<K,V> find(int h, Object k) { ... }
  11. }

原子操作和Unsafe类

这里有三个重要的原子操作,使用这些操作而不需要加锁保证了ConcurrentHashMap的性能。

  1. /**
  2. * 三个重要的原子操作
  3. * ((long)i << ASHIFT) + ABASE 用来计算在内存中的偏移量
  4. * ASHIFT是指tab[i]中第i个元素在相对于数组第一个元素的偏移量,而ABASE是数组的第一个位置的元素在内存中的偏移地址
  5. */
  6. // 获取i处Node,即tab[i]
  7. static final <K,V> ConcurrentHashMap.Node<K,V> tabAt(ConcurrentHashMap.Node<K,V>[] tab, int i) {
  8. return (ConcurrentHashMap.Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
  9. }
  10. // 利用CAS算法设置i位置上的Node节点(将c和tab[i]比较,相同则插入v)
  11. static final <K,V> boolean casTabAt(ConcurrentHashMap.Node<K,V>[] tab, int i,
  12. ConcurrentHashMap.Node<K,V> c, ConcurrentHashMap.Node<K,V> v) {
  13. // CAS算法:无阻塞,通过自旋来实现不断比较期望值与当前值,若相等,则修改,否则一直自旋(与乐观锁思想相似)
  14. return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
  15. }
  16. // 设置节点位置的值
  17. static final <K,V> void setTabAt(ConcurrentHashMap.Node<K,V>[] tab, int i, ConcurrentHashMap.Node<K,V> v) {
  18. U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
  19. }

Unsafe类提供了很多操作。例如获取元素的地址等和各种CAS操作。

可以看下 Unsafe介绍

  1. // Unsafe,U
  2. private static final sun.misc.Unsafe U;
  3. private static final long SIZECTL;
  4. private static final long TRANSFERINDEX;
  5. private static final long BASECOUNT;
  6. private static final long CELLSBUSY;
  7. private static final long CELLVALUE;
  8. private static final long ABASE;
  9. private static final int ASHIFT;
  10. static {
  11. try {
  12. U = sun.misc.Unsafe.getUnsafe();
  13. Class<?> k = ConcurrentHashMap.class;
  14. // 获取ConcurrentHashMap这个对象字段sizeCtl在内存中的偏移量
  15. SIZECTL = U.objectFieldOffset
  16. (k.getDeclaredField("sizeCtl"));
  17. TRANSFERINDEX = U.objectFieldOffset
  18. (k.getDeclaredField("transferIndex"));
  19. BASECOUNT = U.objectFieldOffset
  20. (k.getDeclaredField("baseCount"));
  21. CELLSBUSY = U.objectFieldOffset
  22. (k.getDeclaredField("cellsBusy"));
  23. Class<?> ck = CounterCell.class;
  24. CELLVALUE = U.objectFieldOffset
  25. (ck.getDeclaredField("value"));
  26. Class<?> ak = Node[].class;
  27. // 获取数组第一个元素的偏移地址
  28. ABASE = U.arrayBaseOffset(ak);
  29. // arrayIndexScale可以获取数组的转换因子,也就是数组中元素的增量地址
  30. int scale = U.arrayIndexScale(ak);
  31. if ((scale & (scale - 1)) != 0)
  32. throw new Error("data type scale not a power of two");
  33. ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
  34. } catch (Exception e) {
  35. throw new Error(e);
  36. }
  37. }

重要方法

初始化表操作(initTable)

这个方法的目的是初始化一个table。

  1. // 初始化表
  2. private final ConcurrentHashMap.Node<K,V>[] initTable() {
  3. ConcurrentHashMap.Node<K,V>[] tab; int sc;
  4. while ((tab = table) == null || tab.length == 0) {
  5. // 如果已经创建过了则让行
  6. if ((sc = sizeCtl) < 0)
  7. Thread.yield(); // lost initialization race; just spin
  8. // CAS操作,若SIZECTL和sc相同,则将SIZECTL修改为-1(表示正在初始化)
  9. else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
  10. try {
  11. if ((tab = table) == null || tab.length == 0) {
  12. // 创建表
  13. int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
  14. @SuppressWarnings("unchecked")
  15. ConcurrentHashMap.Node<K,V>[] nt = (ConcurrentHashMap.Node<K,V>[])new ConcurrentHashMap.Node<?,?>[n];
  16. table = tab = nt;
  17. //相当于sc=0.75*n 设置一个扩容的阈值
  18. sc = n - (n >>> 2);
  19. }
  20. } finally {
  21. sizeCtl = sc;
  22. }
  23. break;
  24. }
  25. }
  26. return tab;
  27. }

插入键值对(put和putVal)

put操作和putVal的操作的关系只是一个调用关系,在这就不提put操作了,重点在于putVal。

这里的操作流程是:

  • 先计算传入key的哈希值hash。
  • 进入for循环自旋直到完成插入操作。
    1. 如果表还未初始化,则去初始化表。
    2. 如果hash位置对应的桶还未初始化,就用CAS操作去插入新的键值对并退出自旋。
    3. 如果是ForwardingNode就调用 helpTransfer() 去帮忙将旧表复制到新表中。
    4. 否则就是需要将新的键值对放到链表或者树上了。(具体看代码)
  • 最后调用 addCount() 使得元素数目+1,这里如果不够空间也会在 addCount() 中扩容。
  1. final V putVal(K key, V value, boolean onlyIfAbsent) {
  2. // 不允许key或value为null,这里和HashMap不一样
  3. if (key == null || value == null) throw new NullPointerException();
  4. /*
  5. * static final int spread(int h) {
  6. * return (h ^ (h >>> 16)) & HASH_BITS;
  7. * }
  8. * static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
  9. * 计算哈希
  10. */
  11. int hash = spread(key.hashCode());
  12. int binCount = 0;
  13. // 自旋操作,只有成功插入了才会跳出
  14. for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
  15. ConcurrentHashMap.Node<K,V> f; int n, i, fh;
  16. // 表还未初始化
  17. if (tab == null || (n = tab.length) == 0)
  18. tab = initTable();
  19. // 该位置还没有初始化
  20. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  21. // 使用CAS插入新的键值对,不需要加锁
  22. if (casTabAt(tab, i, null,
  23. new ConcurrentHashMap.Node<K,V>(hash, key, value, null)))
  24. break; // no lock when adding to empty bin
  25. }
  26. // 如果是Forwording Node就帮忙transfer整合表
  27. else if ((fh = f.hash) == MOVED)
  28. tab = helpTransfer(tab, f);
  29. else {
  30. V oldVal = null;
  31. // 这里使用synchronized同步table中目标位置的bucket,即tab[i],相当于分段锁,保证线程安全
  32. synchronized (f) {
  33. if (tabAt(tab, i) == f) {
  34. // 如果是链表结点
  35. if (fh >= 0) {
  36. binCount = 1;
  37. // 遍历链表
  38. for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
  39. K ek;
  40. // 在原链表找到了key就覆盖值
  41. if (e.hash == hash &&
  42. ((ek = e.key) == key ||
  43. (ek != null && key.equals(ek)))) {
  44. oldVal = e.val;
  45. if (!onlyIfAbsent)
  46. e.val = value;
  47. break;
  48. }
  49. ConcurrentHashMap.Node<K,V> pred = e;
  50. // 找到链表末尾还找不到就在末尾插入新的键值对
  51. if ((e = e.next) == null) {
  52. pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
  53. value, null);
  54. break;
  55. }
  56. }
  57. }
  58. // 如果是树的结点
  59. else if (f instanceof ConcurrentHashMap.TreeBin) {
  60. ConcurrentHashMap.Node<K,V> p;
  61. binCount = 2;
  62. // 树的操作
  63. if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
  64. value)) != null) {
  65. oldVal = p.val;
  66. if (!onlyIfAbsent)
  67. p.val = value;
  68. }
  69. }
  70. }
  71. }
  72. // 为1表示为链表结点,为2表示为树结点,为0表示插入新的结点
  73. if (binCount != 0) {
  74. // 判断是否达到变成树的阈值(默认为8),达到了就将tab[i]的链表重构为红黑树
  75. if (binCount >= TREEIFY_THRESHOLD)
  76. treeifyBin(tab, i);
  77. if (oldVal != null)
  78. return oldVal;
  79. break;
  80. }
  81. }
  82. }
  83. // 当前map的元素+1
  84. addCount(1L, binCount);
  85. return null;
  86. }

helpTransfer

这个方法是发现结点是 ForwardingNode 类时候调用的,进入其中帮助 transfer。

  1. /**
  2. * Helps transfer if a resize is in progress.
  3. * 辅助方法,f为ForwardingNode进入,细节个人还没理解透彻
  4. */
  5. final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
  6. Node<K,V>[] nextTab; int sc;
  7. if (tab != null && (f instanceof ForwardingNode) &&
  8. (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
  9. // 返回一个扩容校验标识
  10. int rs = resizeStamp(tab.length);
  11. // 当处于扩容状态
  12. while (nextTab == nextTable && table == tab &&
  13. (sc = sizeCtl) < 0) {
  14. // 判断校验标识是否相等,如果校验符不等或者扩容操作已经完成了,直接退出循环,不用协助它们扩容了
  15. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  16. sc == rs + MAX_RESIZERS || transferIndex <= 0)
  17. break;
  18. // 否则调用transfer帮助它们进行扩容,sc+1标识增加了一个线程进行扩容
  19. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
  20. transfer(tab, nextTab);
  21. break;
  22. }
  23. }
  24. return nextTab;
  25. }
  26. return table;
  27. }

扩容操作(transfer)

当空间不够的时候,要将旧的表(table)复制到新的表(nextTab)中。这是个人觉得最复杂的操作了。

操作流程:

  • 如果新的表为空,就初始化新的表(单线程操作),新的表的容量为原来的2倍。
  • 新建一个 ForwardingNode 用来标志已经完成扩容的结点。
  • 自旋直到处理完毕
    • 为线程分配工作的区间,逆序处理每一个桶。
    • 遍历桶。
    • 如果该桶为空,就将 ForwardingNode 放入标志已经处理过。
    • 如果该桶为 ForwardingNode 就跳过。
    • 否则使用 synchronized 锁住桶,进行复制转移操作(详见注释,和HashMap有点像)。完成后,标记为 ForwardingNode。
    • 当finish后,则将原来的table更新为nextTab,然后将nextTable设为null帮助GC。
  1. /**
  2. * Moves and/or copies the nodes in each bin to new table. See
  3. * above for explanation.
  4. * 扩容,将旧的表的元素放到新的表中
  5. */
  6. private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
  7. int n = tab.length, stride;
  8. if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
  9. stride = MIN_TRANSFER_STRIDE; // subdivide range
  10. // 如果新的表为空,则需要初始化新的表
  11. if (nextTab == null) { // initiating
  12. try {
  13. // 扩展为原容量的2倍(n<<1),这个操作是单线程完成的,因为这初始化了nextTab
  14. @SuppressWarnings("unchecked")
  15. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
  16. nextTab = nt;
  17. } catch (Throwable ex) { // try to cope with OOME
  18. sizeCtl = Integer.MAX_VALUE;
  19. return;
  20. }
  21. nextTable = nextTab;
  22. // transferIndex指向最后一个桶,从后往前遍历
  23. transferIndex = n;
  24. }
  25. // 下面是并发扩容的核心
  26. int nextn = nextTab.length;
  27. // 新建一个ForwardingNode,用于标识已经完成复制转移的桶
  28. ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
  29. // 如果等于true,那么说明这个节点已经处理过
  30. boolean advance = true;
  31. // 标识是否所有结点复制完成
  32. boolean finishing = false; // to ensure sweep before committing nextTab
  33. // i是当前处理的桶,bound是区间下界
  34. for (int i = 0, bound = 0;;) {
  35. Node<K,V> f; int fh;
  36. while (advance) {
  37. int nextIndex, nextBound;
  38. // 通过 --i 去遍历桶
  39. if (--i >= bound || finishing)
  40. advance = false;
  41. // 说明已经没有需要复制转移的桶了
  42. else if ((nextIndex = transferIndex) <= 0) {
  43. i = -1;
  44. advance = false;
  45. }
  46. // 为当前线程分配任务,处理的桶结点区间为(nextBound,nextIndex)
  47. else if (U.compareAndSwapInt
  48. (this, TRANSFERINDEX, nextIndex,
  49. nextBound = (nextIndex > stride ?
  50. nextIndex - stride : 0))) {
  51. bound = nextBound;
  52. i = nextIndex - 1;
  53. advance = false;
  54. }
  55. }
  56. if (i < 0 || i >= n || i + n >= nextn) {
  57. int sc;
  58. // 所有结点复制完成
  59. if (finishing) {
  60. nextTable = null;
  61. table = nextTab;
  62. // 扩容阈值为原来的1.5倍,即现在的0.75倍
  63. sizeCtl = (n << 1) - (n >>> 1);
  64. return;
  65. }
  66. // CAS将sizeCtl-1,说明新的线程加入到扩容操作中
  67. if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
  68. if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
  69. return;
  70. finishing = advance = true;
  71. i = n; // recheck before commit
  72. }
  73. }
  74. // 如果是空结点,就将ForwardingNode放入用来标志已经被处理过
  75. else if ((f = tabAt(tab, i)) == null)
  76. advance = casTabAt(tab, i, null, fwd);
  77. // 如果是ForwardingNode,那么这个点已经被处理过,跳过
  78. else if ((fh = f.hash) == MOVED)
  79. advance = true; // already processed
  80. else {
  81. // 否则进入转移
  82. synchronized (f) {
  83. if (tabAt(tab, i) == f) {
  84. Node<K,V> ln, hn;
  85. // 是一个链表结点
  86. if (fh >= 0) {
  87. // 这里和HashMap一样,只要判断扩容后的那一位是1还是0就可以知道是放在原位i还是放到i+n的桶
  88. int runBit = fh & n;
  89. Node<K,V> lastRun = f;
  90. // 遍历找到桶中最后连续的 fh&n 不变的结点
  91. for (Node<K,V> p = f.next; p != null; p = p.next) {
  92. int b = p.hash & n;
  93. if (b != runBit) {
  94. runBit = b;
  95. lastRun = p;
  96. }
  97. }
  98. // 如果是0,那么就让链表头为ln(放到原位i的链表)
  99. if (runBit == 0) {
  100. ln = lastRun;
  101. hn = null;
  102. } // 如果是1,那么让链表头为hn(放到i+n的链表)
  103. else {
  104. hn = lastRun;
  105. ln = null;
  106. }
  107. /**
  108. * 如果是ln一开始有值的话,那么就是lastRun后面那一段是顺序的,然后其余的一个个插入到lastRun前面
  109. * 如果是hn一开始有值的话,也是同理
  110. */
  111. for (Node<K,V> p = f; p != lastRun; p = p.next) {
  112. int ph = p.hash; K pk = p.key; V pv = p.val;
  113. /**
  114. * Node(int hash, K key, V val, Node<K,V> next) {
  115. * this.hash = hash;
  116. * this.key = key;
  117. * this.val = val;
  118. * this.next = next;
  119. * }
  120. * 这里Node的构造函数是让新的结点的next指向传入的结点
  121. * 即会让链表逆序
  122. */
  123. if ((ph & n) == 0)
  124. ln = new Node<K,V>(ph, pk, pv, ln);
  125. else
  126. hn = new Node<K,V>(ph, pk, pv, hn);
  127. }
  128. // 将两条链表放入到nextTab的相应的桶中
  129. setTabAt(nextTab, i, ln);
  130. setTabAt(nextTab, i + n, hn);
  131. // 将原来的桶的第i位标识为已经处理
  132. setTabAt(tab, i, fwd);
  133. advance = true;
  134. }
  135. // 如果是树的结点,就用红黑树的复制算法
  136. else if (f instanceof TreeBin) {
  137. TreeBin<K,V> t = (TreeBin<K,V>)f;
  138. TreeNode<K,V> lo = null, loTail = null;
  139. TreeNode<K,V> hi = null, hiTail = null;
  140. int lc = 0, hc = 0;
  141. for (Node<K,V> e = t.first; e != null; e = e.next) {
  142. int h = e.hash;
  143. TreeNode<K,V> p = new TreeNode<K,V>
  144. (h, e.key, e.val, null, null);
  145. if ((h & n) == 0) {
  146. if ((p.prev = loTail) == null)
  147. lo = p;
  148. else
  149. loTail.next = p;
  150. loTail = p;
  151. ++lc;
  152. }
  153. else {
  154. if ((p.prev = hiTail) == null)
  155. hi = p;
  156. else
  157. hiTail.next = p;
  158. hiTail = p;
  159. ++hc;
  160. }
  161. }
  162. ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
  163. (hc != 0) ? new TreeBin<K,V>(lo) : t;
  164. hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
  165. (lc != 0) ? new TreeBin<K,V>(hi) : t;
  166. setTabAt(nextTab, i, ln);
  167. setTabAt(nextTab, i + n, hn);
  168. setTabAt(tab, i, fwd);
  169. advance = true;
  170. }
  171. }
  172. }
  173. }
  174. }
  175. }

addCount()

addCount,更新baseCount并判断table数组是否太小需要扩容。

核心思想是在并发较低时,只需更新base值。在高并发的情况下,将对单一值的更新转化为数组上元素的更新,以降低并发争用。总的映射个数为base+CounterCell各个元素的和。如果总数大于阈值,扩容。

  1. private final void addCount(long x, int check) {
  2. // 初始化时counterCells为空,在并发量很高时,如果存在两个线程同时执行CAS修改baseCount值,
  3. // 则失败的线程会继续执行方法体中的逻辑,使用CounterCell记录元素个数的变化
  4. CounterCell[] as; long b, s;
  5. // 更新baseCount为s
  6. if ((as = counterCells) != null ||
  7. !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
  8. // 更新失败则进入
  9. CounterCell a; long v; int m;
  10. // uncontended表示更新CounterCell是否存在争用
  11. boolean uncontended = true;
  12. // CAS更新CounterCell数组的值+x
  13. if (as == null || (m = as.length - 1) < 0 ||
  14. (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
  15. !(uncontended =
  16. U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
  17. // 还是失败就要用fullAddCount(),作用是将x添加到counterCells[]或baseCount中
  18. fullAddCount(x, uncontended);
  19. return;
  20. }
  21. if (check <= 1)
  22. return;
  23. // 统计元素个数
  24. s = sumCount();
  25. }
  26. if (check >= 0) {
  27. Node<K,V>[] tab, nt; int n, sc;
  28. // 元素个数大于扩容阈值就要扩容
  29. while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
  30. (n = tab.length) < MAXIMUM_CAPACITY) {
  31. int rs = resizeStamp(n);
  32. // 正在扩容
  33. if (sc < 0) {
  34. // 扩容结束或者没有桶分配就break
  35. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  36. sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
  37. transferIndex <= 0)
  38. break;
  39. // CAS更新正在扩容的线程+1
  40. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
  41. transfer(tab, nt);
  42. }
  43. // 当前只有一个线程在扩容(进来的该线程),就去扩容
  44. else if (U.compareAndSwapInt(this, SIZECTL, sc,
  45. (rs << RESIZE_STAMP_SHIFT) + 2))
  46. transfer(tab, null);
  47. // 实时监测
  48. s = sumCount();
  49. }
  50. }
  51. }

总结

  • JDK1.8使用了CAS操作和synchronized来实现并发,但是这里的synchronized只是锁住正在操作的桶而已,并没有锁住整个map。而JDK1.7使用了分段锁来实现并发,一个segment对应了多个桶。
  • JDK1.8使用链表+红黑树来实现,在冲突很多的情况下时间复杂度优化了许多。
  • 阅读了HashMap源码后再看ConcurrentHashMap简单了一些,两者有很多共通之处。

Reference

JDK1.8之ConcurrentHashMap的更多相关文章

  1. java并发:jdk1.8中ConcurrentHashMap源码浅析

    ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1. ...

  2. jdk1.8 HashMap & ConcurrentHashMap

    JDK1.8逐字逐句带你理解ConcurrentHashMap https://blog.csdn.net/u012403290 JDK1.8理解HashMap https://blog.csdn.n ...

  3. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

  4. 基于JDK1.8的ConcurrentHashMap分析

    之前看过ConcurrentHashMap的分析,感觉也了解的七七八八了.但昨晚接到了面试,让我把所知道的ConcurrentHashMap全部说出来. 然后我结结巴巴,然后应该毫无意外的话就G了,今 ...

  5. 【JUC】JDK1.8源码分析之ConcurrentHashMap(一)

    一.前言 最近几天忙着做点别的东西,今天终于有时间分析源码了,看源码感觉很爽,并且发现ConcurrentHashMap在JDK1.8版本与之前的版本在并发控制上存在很大的差别,很有必要进行认真的分析 ...

  6. JDK1.7 ConcurrentHashMap 源码浅析

    概述 ConcurrentHashMap是HashMap的线程安全版本,使用了分段加锁的方案,在高并发时有比较好的性能. 本文分析JDK1.7中ConcurrentHashMap的实现. 正文 Con ...

  7. java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)

    1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用 ...

  8. ConcurrentHashmap详解以及在JDK1.8的更新

    因为hashmap本身是非线程安全的,如果多线程对hashmap进行put操作的话,就会导致死循环等现象.ConcurrentHashMap主要就是为了应对hashmap在并发环境下不安全而诞生的,C ...

  9. 并发容器(四)ConcurrentHashMap 深入解析(JDK1.6)

      这篇文章深入分析的是 JDK1.6的 ConcurrentHashMap 的实现原理,但在JDK1.8中又改进了 ConcurrentHashMap 的实现,废弃了 segments.虽然是已经被 ...

随机推荐

  1. multi-node和generic-pool两大利器

    1.multi-node node只能单进程,单cpu工作,而multi-node则可以让node在多进程下共享内存的工作,实现机制是依靠child_process的sendmsg做到的.想要了解具体 ...

  2. WPF自定义窗口最大化显示任务栏

    原文:WPF自定义窗口最大化显示任务栏 当我们要自定义WPF窗口样式时,通常是采用设计窗口的属性 WindowStyle="None" ,然后为窗口自定义放大,缩小,关闭按钮的样式 ...

  3. 用蓝牙连接debian和诺基亚手机

    本方法已经用debian 4.0.诺基亚9300和一个hl-united牌子的USB蓝牙适配器测试过了,效果很好.             1.安装必要的软件包:   #apt-get install ...

  4. SqlServer 无法为可更新的订阅设置发布服务器登录名 sp_link_publication

    原文:SqlServer 无法为可更新的订阅设置发布服务器登录名 sp_link_publication 没有截图: 创建可更新订阅,正常创建了发布,在订阅端创建订阅,最后一步提示完成,却出现了警告: ...

  5. ASP.NET MVC 下UpdateModel可空未填写的参数为Null,为何不是空字符串

    查了好久,终于收到原因: if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && Object.Equals(valu ...

  6. 发布ActiveX控件

    最近我们正在研究ActiveX技术.我们使用Delphi 5创建了一个具有ActiveForm的ActiveX控件应用程序.这个控件产生一个.OCX文件.现在,我们需要把这个控件部署在服务器端,在用户 ...

  7. C# 设置IP地址及设置自动获取IP

    原文:C# 设置IP地址及设置自动获取IP </pre><pre name="code" class="csharp">1.添加引用&q ...

  8. SQL Server上唯一的数据库集群:负载均衡、读写分离、容灾(数据零丢失、服务高可用)

    SQL Server上唯一的数据库集群:负载均衡.读写分离.容灾(数据零丢失.服务高可用).审计.优化,全面解决数据库用户问题.一键安装,易用稳定,性价比高,下载链接:http://www.zheti ...

  9. HUSTOJ的Windows版评判内核(限制内存使用)

    HUSTOJ的Windows版评判内核(一) 作者:游蓝海 个人主页:http://blog.csdn.net/you_lan_hai 2013.4.9 注:最新版本项目地址:https://gith ...

  10. Delphi xe5 StyleBook的用法(待续)

    首先要在FORM里拖进来一个StyleBook1,然后在Form里设置属性,记住一定要在单击form,在OBject Inspector里设置StyleBook  [StyleBook1]. 下一个属 ...